http://wiki.mios.com/api.php?action=feedcontributions&user=A-lurker&feedformat=atomMiOS - User contributions [en]2024-03-28T16:30:09ZUser contributionsMediaWiki 1.19.8http://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2018-03-27T07:51:22Z<p>A-lurker: /* function: attr_get */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
Note: if an attribute is modified, it will not persist between Luup engine restarts. You need to use [[Luup_Lua_extensions#function:_attr_set|luup.attr_set]] instead. Eg:<br />
<source lang="lua"><br />
local ipAddress = '192.168.l.l2'<br />
<br />
-- this will not persist between restarts<br />
luup.devices[lul_device].ip = ipAddress<br />
<br />
-- this will persist between restarts<br />
luup.attr_set('ip', ipAddress, lul_device)</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only ''error'' will be returned with a value of -1. Otherwise, all 4 values are returned. ''error'' is 0 if the UPnP device reported the action was successful. ''arguments'' is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer.<br />
<br />
{{Warning|Currently only Z-Wave and Insteon jobs are returned in the ''job'' parameter.}}<br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local resultCode, resultString, job, returnArguments = luup.call_action("urn:upnp-org:serviceId:Dimming1", "SetLoadLevelTarget", {["newLoadlevelTarget"] = "50"}, 5)</source><br />
<br><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], refresh_user_data (boolean)<br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc. Like [[#function:_attr_get|attr_get]], if the device is zero it sets the top-level user_data json tag.<br />
If 'refresh_user_data' is ''true'', setting the attribute will increment the DataVersion in the user_data.<br />
<br />
=== function: attr_get ===<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: if the [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] is valid, then the attribute is returned as a string.<br />
<br />
If the [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] is valid but the attribute doesn't exist, then the returned string is empty.<br />
<br />
If the [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] is invalid and due to a MIOS coding problem, the expected ''''nil' is not returned''' (as of March 2018). Instead '(null)' is returned.<br />
<br />
Gets the requested attribute for the device: Examples of attributes are 'mac', 'name', 'id', etc. If the number 0 is passed as the device, it gets the top level attribute from the master userdata, like 'firmware_version', etc.<br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* '''device_type''' (string) : This is the device type and should be set to an empty string, so it will be retrieved from the UPnP device file.<br />
* '''internal_id''' (string) : This is the device '''altid'''.<br />
* '''description''' (string) : This is the device name.<br />
* '''upnp_file''' (string) : This is the UPnP device file.<br />
* '''upnp_impl''' (string) : This is the implementation file.<br />
* '''ip''' (string)<br />
* '''mac''' (string)<br />
* '''hidden''' (boolean)<br />
* '''invisible''' (boolean)<br />
* '''parent''' (number) : The device ID of the parent device. Set it to '0' if this device shouldn't have any parents.<br />
* '''room''' (number) : The room ID. If set to '0', the device won't be put in any room.<br />
* '''pluginnum''' (number) : Which plugin to install after the device is created. Set it to '0' if no plugin should be installed.<br />
* '''statevariables''' (string) : Variables and attributes you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use '<code>,</code>' and '<code>=</code>' to separate service, variable and value, like this: <code>service,variable=value\nservice,variable=value</code><br />
To set an attribute, leave "service" empty, like this: <code>,variable=value</code><br />
Example: <source lang="lua">"urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50\n,manufacturer=MiOS"</source><br />
* '''pnpid''' (number) : If this is a device from KitDevice.json, this is the ''PK_KitDevice'' number. Otherwise set this to '0'.<br />
* '''nochildsync''' (string) : If set to "1", ignore this device when syncing child devices. Unless you know what you're doing, set it to an empty string.<br />
* '''aeskey''' (string) : This should be set to an empty string.<br />
* '''reload''' (boolean) : If ''true'', the Luup engine will be restarted after the device is created.<br />
* '''nodupid''' (boolean) : Set this to ''false''.<br />
<br />
returns: the device ID<br />
<br />
{{Warning|All parameters are mandatory.}}<br />
{{Important|<tt>device_type</tt> '''MUST''' be either an '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.}}<br><br />
This creates the device with the parameters given, and returns the device ID.<br />
Example: <source lang="lua">luup.create_device("", "testdevice", "Test Device", "D_BinaryLight1.xml", "", "", "", false, false, 0, 0, 0, ",manufacturer=MiOS", 0, "", "", true, false)</source><br />
<br><br />
<br />
=== function: device_message ===<br />
<br />
''<span style="color: red">Available in releases after Feb 2017</span>''<br />
<br />
This adds a system message that is attached to a device and appears in the UI under the device.<br />
<br />
parameters:<br />
* '''device_id''' (number) : This is the device id number<br />
* '''status''' (int) : This is the status of message, and corresponds to the job status, and generally determines what color the message appears:<br />
** -1 : No job: black message<br />
** 0 : Job waiting to start: <span style="color: blue">blue</span> message<br />
** 1 : Job in progress: <span style="color: blue">blue</span> message<br />
** 2 : Job error: <span style="color: red">red</span> message<br />
** 3 : Job aborted: <span style="color: red">red</span> message<br />
** 4 : Job done: <span style="color: green">green</span> message<br />
** 5 : Job waiting for callback: <span style="color: blue">blue</span> message<br />
** 6 : Job requeue: <span style="color: blue">blue</span> message<br />
** 7 : Job in progress with pending data: black message<br />
* '''message''' (string) : This is the text that appears in the message.<br />
* '''timeout''' (int) : This is the number of seconds to display the message. Pass 0 to display it indefinitely<br />
* '''source''' (string) : This is the source module of the message. It can be anything, and is generally informational. It is recommended to use the name of the luup plugin.<br />
<br />
return: nothing<br />
<br />
{{Warning|All parameters are mandatory.}}<br />
<br><br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns: operationStatusCode (Number), content (String), httpStatusCode (Number)<br />
<br />
This reads the URL and returns 3 variables: the first is a numeric error code which is 0 if successful. The second variable is a string containing the contents of the page. The third variable is the HTTP status code. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
"append" is a misnomer as every time you use the luup.chdev module, you must enumerate all the children:<br />
<br />
* if you do not "append" an existing child, that child will be deleted<br />
* if you "append" an existing child, you have the opportunity to change its parameters. All the parameters are required, even for existing children.<br />
* if you "append" a non-existent child, it will be created.<br />
<br />
If you have the situation where child devices may come and go; eg say children are created for a WiFi sensor but the WiFi drops out on occasion, then you need to keep a record of the all the children, so they can all be enumerated at start up. Otherwise missing children will be deleted. If they reappear, say WiFi reconnects in this example, then the children will be added back in but will have a new device id.<br />
<br />
When the final luup.chdev.sync is executed the children are checked. Any additions, changes or deletions will result in a Luup engine restart. Any incorrect coding of luup.chdev.append may result in a situation where the engine goes into a loop and continually restarts. As an example, if two children are added with the same id parameter, this will occur. To recover, you need to quickly upload a new file that has the faulty "append" code commented out. In the interim the engine will be incrementing the device ID numbers with no end in sight.<br />
<br />
parameters:<br />
* [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
* ptr (binary object)<br />
* id (string) Note: labeled "altid" in the UI<br />
* description (string)<br />
* device_type (string)<br />
* device_filename (string)<br />
* implementation_filename (string)<br />
* parameters (string)<br />
* embedded (boolean)<br />
* [invisible (boolean)] optional<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
{{Important|On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.}}<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2018-03-10T03:45:14Z<p>A-lurker: /* function: append */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
Note: if an attribute is modified, it will not persist between Luup engine restarts. You need to use [[Luup_Lua_extensions#function:_attr_set|luup.attr_set]] instead. Eg:<br />
<source lang="lua"><br />
local ipAddress = '192.168.l.l2'<br />
<br />
-- this will not persist between restarts<br />
luup.devices[lul_device].ip = ipAddress<br />
<br />
-- this will persist between restarts<br />
luup.attr_set('ip', ipAddress, lul_device)</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only ''error'' will be returned with a value of -1. Otherwise, all 4 values are returned. ''error'' is 0 if the UPnP device reported the action was successful. ''arguments'' is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer.<br />
<br />
{{Warning|Currently only Z-Wave and Insteon jobs are returned in the ''job'' parameter.}}<br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local resultCode, resultString, job, returnArguments = luup.call_action("urn:upnp-org:serviceId:Dimming1", "SetLoadLevelTarget", {["newLoadlevelTarget"] = "50"}, 5)</source><br />
<br><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], refresh_user_data (boolean)<br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc. Like [[#function:_attr_get|attr_get]], if the device is zero it sets the top-level user_data json tag.<br />
If 'refresh_user_data' is ''true'', setting the attribute will increment the DataVersion in the user_data.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If the number 0 is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* '''device_type''' (string) : This is the device type and should be set to an empty string, so it will be retrieved from the UPnP device file.<br />
* '''internal_id''' (string) : This is the device '''altid'''.<br />
* '''description''' (string) : This is the device name.<br />
* '''upnp_file''' (string) : This is the UPnP device file.<br />
* '''upnp_impl''' (string) : This is the implementation file.<br />
* '''ip''' (string)<br />
* '''mac''' (string)<br />
* '''hidden''' (boolean)<br />
* '''invisible''' (boolean)<br />
* '''parent''' (number) : The device ID of the parent device. Set it to '0' if this device shouldn't have any parents.<br />
* '''room''' (number) : The room ID. If set to '0', the device won't be put in any room.<br />
* '''pluginnum''' (number) : Which plugin to install after the device is created. Set it to '0' if no plugin should be installed.<br />
* '''statevariables''' (string) : Variables and attributes you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use '<code>,</code>' and '<code>=</code>' to separate service, variable and value, like this: <code>service,variable=value\nservice,variable=value</code><br />
To set an attribute, leave "service" empty, like this: <code>,variable=value</code><br />
Example: <source lang="lua">"urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50\n,manufacturer=MiOS"</source><br />
* '''pnpid''' (number) : If this is a device from KitDevice.json, this is the ''PK_KitDevice'' number. Otherwise set this to '0'.<br />
* '''nochildsync''' (string) : If set to "1", ignore this device when syncing child devices. Unless you know what you're doing, set it to an empty string.<br />
* '''aeskey''' (string) : This should be set to an empty string.<br />
* '''reload''' (boolean) : If ''true'', the Luup engine will be restarted after the device is created.<br />
* '''nodupid''' (boolean) : Set this to ''false''.<br />
<br />
returns: the device ID<br />
<br />
{{Warning|All parameters are mandatory.}}<br />
{{Important|<tt>device_type</tt> '''MUST''' be either an '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.}}<br><br />
This creates the device with the parameters given, and returns the device ID.<br />
Example: <source lang="lua">luup.create_device("", "testdevice", "Test Device", "D_BinaryLight1.xml", "", "", "", false, false, 0, 0, 0, ",manufacturer=MiOS", 0, "", "", true, false)</source><br />
<br><br />
<br />
=== function: device_message ===<br />
<br />
''<span style="color: red">Available in releases after Feb 2017</span>''<br />
<br />
This adds a system message that is attached to a device and appears in the UI under the device.<br />
<br />
parameters:<br />
* '''device_id''' (number) : This is the device id number<br />
* '''status''' (int) : This is the status of message, and corresponds to the job status, and generally determines what color the message appears:<br />
** -1 : No job: black message<br />
** 0 : Job waiting to start: <span style="color: blue">blue</span> message<br />
** 1 : Job in progress: <span style="color: blue">blue</span> message<br />
** 2 : Job error: <span style="color: red">red</span> message<br />
** 3 : Job aborted: <span style="color: red">red</span> message<br />
** 4 : Job done: <span style="color: green">green</span> message<br />
** 5 : Job waiting for callback: <span style="color: blue">blue</span> message<br />
** 6 : Job requeue: <span style="color: blue">blue</span> message<br />
** 7 : Job in progress with pending data: black message<br />
* '''message''' (string) : This is the text that appears in the message.<br />
* '''timeout''' (int) : This is the number of seconds to display the message. Pass 0 to display it indefinitely<br />
* '''source''' (string) : This is the source module of the message. It can be anything, and is generally informational. It is recommended to use the name of the luup plugin.<br />
<br />
return: nothing<br />
<br />
{{Warning|All parameters are mandatory.}}<br />
<br><br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns: operationStatusCode (Number), content (String), httpStatusCode (Number)<br />
<br />
This reads the URL and returns 3 variables: the first is a numeric error code which is 0 if successful. The second variable is a string containing the contents of the page. The third variable is the HTTP status code. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
"append" is a misnomer as every time you use the luup.chdev module, you must enumerate all the children:<br />
<br />
* if you do not "append" an existing child, that child will be deleted<br />
* if you "append" an existing child, you have the opportunity to change its parameters. All the parameters are required, even for existing children.<br />
* if you "append" a non-existent child, it will be created.<br />
<br />
If you have the situation where child devices may come and go; eg say children are created for a WiFi sensor but the WiFi drops out on occasion, then you need to keep a record of the all the children, so they can all be enumerated at start up. Otherwise missing children will be deleted. If they reappear, say WiFi reconnects in this example, then the children will be added back in but will have a new device id.<br />
<br />
When the final luup.chdev.sync is executed the children are checked. Any additions, changes or deletions will result in a Luup engine restart. Any incorrect coding of luup.chdev.append may result in a situation where the engine goes into a loop and continually restarts. As an example, if two children are added with the same id parameter, this will occur. To recover, you need to quickly upload a new file that has the faulty "append" code commented out. In the interim the engine will be incrementing the device ID numbers with no end in sight.<br />
<br />
parameters:<br />
* [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
* ptr (binary object)<br />
* id (string) Note: labeled "altid" in the UI<br />
* description (string)<br />
* device_type (string)<br />
* device_filename (string)<br />
* implementation_filename (string)<br />
* parameters (string)<br />
* embedded (boolean)<br />
* [invisible (boolean)] optional<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
{{Important|On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.}}<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2016-09-06T00:23:14Z<p>A-lurker: /* variable: devices */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
Note: if an attribute is modified, it will not persist between Luup engine restarts. You need to use [[Luup_Lua_extensions#function:_attr_set|luup.attr_set]] instead. Eg:<br />
<source lang="lua"><br />
local ipAddress = '192.168.l.l2'<br />
<br />
-- this will not persist between restarts<br />
luup.devices[lul_device].ip = ipAddress<br />
<br />
-- this will persist between restarts<br />
luup.attr_set('ip', ipAddress, lul_device)</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only ''error'' will be returned with a value of -1. Otherwise, all 4 values are returned. ''error'' is 0 if the UPnP device reported the action was successful. ''arguments'' is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer.<br />
<br />
{{Warning|Currently only Z-Wave and Insteon jobs are returned in the ''job'' parameter.}}<br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local resultCode, resultString, job, returnArguments = luup.call_action("urn:upnp-org:serviceId:Dimming1", "SetLoadLevelTarget", {["newLoadlevelTarget"] = "50"}, 5)</source><br />
<br><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc. Like attr_get, if the device is zero it sets the top-level user_data json tag.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If the number 0 is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters:<br />
* [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
* ptr (binary object)<br />
* id (string)<br />
* description (string)<br />
* device_type (string)<br />
* device_filename (string)<br />
* implementation_filename (string)<br />
* parameters (string)<br />
* embedded (boolean)<br />
* [invisible (boolean)] optional<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
{{Important|On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.}}<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_VariablesLuup Variables2015-05-28T21:09:17Z<p>A-lurker: /* Examples */</p>
<hr />
<div>[[Category:Development]]<br />
The "Variables" for a device tell you it's current state, such as if it's on or off, what temperature it has, and so on. Variables are given a name and a service ID, which is defined by the UPnP forum. You can use this service ID/variable name pair to get the state of a device in the Luup engine by using the function luup.variable_get, as documented in [[Luup_Lua_extensions]]. You can also see the current value of a device's variables by going into Vera's setup page, click 'Devices', click + next to the device, then click 'Advanced'. The name of every variable for the device is shown along with the current value, and if you move your mouse over the variable name, you will the corresponding service ID in a popup window.<br />
<br />
<br />
=On/Off Switch=<br />
<br />
==Device category==<br />
Device Category 3,<br />
UPnP device id: ''urn:schemas-upnp-org:device:BinaryLight:1''<br />
<br />
==Variables==<br />
{| width="85%" cellpadding="10%" cellspacing="0" border="1"<br />
|+<br />
! align="left" width="30%"|Service!! align="left"|Variable!! align="left"|Description<br />
|-<br />
|''urn:upnp-org:serviceId:SwitchPower1''||''Status''||If the device is on, the value is ''1'', otherwise it's ''0''.<br />
|}<br />
<br />
==Examples==<br />
* Getting whether the Switch/Light is on or off<br />
<source lang="lua"><br />
local switchOnOff = luup.variable_get("urn:upnp-org:serviceId:SwitchPower1", "Status", 37)<br />
if (switchOnOff == "1") then<br />
-- Switch is on<br />
...<br />
end -- if<br />
</source> <br />
<br />
* Request that the Switch be turned on. This will, eventually, set the variable after the device has responded to the request<br />
<source lang="lua"><br />
luup.call_action("urn:upnp-org:serviceId:SwitchPower1", "SetTarget", {newTargetValue = "1"}, 37)<br />
</source><br />
<br />
=Dimmable Light=<br />
<br />
==Device category==<br />
Device category 2, <br />
UPnP device id: ''urn:schemas-upnp-org:device:DimmableLight:1''<br />
<br />
==Variables==<br />
Dimmable lights contain the same variable of an On/Off Switch to indicate the current on/off value, and, if it's on, ''LoadLevelStatus'' indicates the dim level.<br />
<br />
{| width="85%" cellpadding="10%" cellspacing="0" border="1"<br />
|+<br />
! align="left" width="30%"|Service!! align="left"|Variable!! align="left"|Description<br />
|-<br />
|''urn:upnp-org:serviceId:Dimming1''||''LoadLevelStatus''||If the device is off (see On/Off switch) this value indicates the last known dim level, if it's on, this value is the actual dim level. The value is a number from ''0-100'' indicating a percentage of brightness.<br />
|}<br />
<br />
==Examples==<br />
* Getting the current light/load level<br />
<source lang="lua"><br />
local lightLevel = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelStatus", 37)<br />
</source> <br />
<br />
* Request that the Dimmer be set to 50% light/load level. This will, eventually, set the variable after the device has responded to the request<br />
<source lang="lua"><br />
luup.call_action("urn:upnp-org:serviceId:Dimming1", "SetLoadLevelTarget", {newLoadlevelTarget = "50"}, 19)<br />
</source><br />
<br />
<br />
=Thermostat=<br />
<br />
==Device category==<br />
Device category 5,<br />
UPnP device id: ''urn:schemas-upnp-org:device:HVAC_ZoneThermostat:1''<br />
<br />
==Variables==<br />
{| width="85%" cellpadding="10%" cellspacing="0" border="1"<br />
|+<br />
! align="left" width="30%"|Service!! align="left"|Variable!! align="left"|Description<br />
|-<br />
|''urn:upnp-org:serviceId:HVAC_UserOperatingMode1''||''ModeStatus''||This indicates the current operating mode and will be one of the following basic values: <br />
* ''Off''<br />
* ''HeatOn''<br />
* ''CoolOn''<br />
* ''AutoChangeOver''<br />
<br />
In addition to the basic modes, some thermostats may also support the following modes:<br />
* ''InDeadBand''<br />
* ''AuxHeatOn''<br />
* ''EconomyHeatOn''<br />
* ''EmergencyHeatOn''<br />
* ''AuxCoolOn''<br />
* ''EconomyCoolOn''<br />
* ''BuildingProtection''<br />
* ''EnergySavingsHeating''<br />
* ''EnergySavingsCooling'' <br />
|-<br />
|''urn:upnp-org:serviceId:TemperatureSetpoint1_Heat''||''CurrentSetpoint''||This indicates the current heat set point.<br />
|-<br />
|''urn:upnp-org:serviceId:TemperatureSetpoint1_Cool''||''CurrentSetpoint''||This indicatea the current cool set point.<br />
|-<br />
|''urn:upnp-org:serviceId:TemperatureSensor1''||''CurrentTemperature''||This indicates the current ambient temperature.<br />
|}<br />
<br />
==Examples==<br />
* Getting the current temperature<br />
<source lang="lua"><br />
local outsideTemp = luup.variable_get("urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature", 5)<br />
</source><br />
<br />
<br />
=Other Devices or Services=<br />
You can find other variables by looking at code. For example, looking at the code for the LUUP Weather plugin, we can see where the "serviceID" for Current Conditions is ''urn:micasaverde-com:serviceId:Weather1'' (taken from the XML file):<br />
<br />
<source lang="lua"><br />
-- Store the current Condition (eg. "rain")<br />
luup.variable_set("urn:upnp-micasaverde-com:serviceId:Weather1", "ConditionGroup",<br />
condition, PARENT_DEVICE)<br />
</source><br />
<br />
From this we can get the Current Weather ConditionGroup using<br><br />
Service: ''urn:upnp-micasaverde-com:serviceId:Weather1'' Variable name: ''ConditionGroup''<br />
<br />
All we have to add is the device ID that Vera creates. In my case #''37''</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Plugins_ByHandLuup Plugins ByHand2015-02-16T21:37:29Z<p>A-lurker: /* */</p>
<hr />
<div>[[Category:Development]]<br />
==Description of the XML files==<br />
<br />
Because the web generator is not yet operational (as of June 2013) you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins (in UI5, choose Apps, Develop Apps, Luup Files). There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click view to see the XML file. We recommend using the Firefox web browser because it has built-in support for nicely displaying XML files in a graphical tree. XML can be hard to read in other web browser.<br />
<br />
When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.<br />
<br />
The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.<br />
<br />
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.<br />
<br />
The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.<br />
<br />
== The Luup XML implementation file ==<br />
<br />
I_GC100.xml is a full featured, functional implementation for the Global Cache GC100, which is an ethernet device with relay switches, input sensors, infrared transmitters and serial ports. I_GC100.xml shows a complete Luup plugin with parent/child devices. The corresponding UPnP Device specification file is D_GC100.xml. In all the implementation files the top-level XML tag (ie root node) is called "implementation". It contains the following elements: <br />
<br />
=== <settings> ===<br />
<br />
The settings node contains various settings for the implementation. <br />
<br />
=== <protocol> ===<br />
Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. The protocol tag tells Luup what's considered a single ''chunk'' of data. By using a format, from the supported list below, you avoid byte-by-byte processing on input streams as the Luup engine will ''chunk'' the data to you and pass it to your Lua code handling <tt><incoming></tt> requests.<br />
Lua code is much cleaner when it handles data in chunks. If you have a protocol that's not natively supported, and is likely to be used by other devices, let us know and we'll add it to the Luup engine so you don't need to mess with it.<br />
<br />
Valid values for this tag are:<br />
*<tt>cr</tt> - all incoming commands are terminated with a carriage return+line character, and all outgoing data should have a cr appended. Incoming data will have the cr stripped off.<br />
*<tt>crlf</tt> - all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. Incoming data will have the cr/lf stripped off.<br />
*<tt>stxetx</tt> - all incoming commands are surrounded by STX and ETX characters. If you send the string "test" the framework will add the STX before and the ETX at the end, and if the string "<tt>''<stx>''test''<etx>''</tt>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.<br />
*<tt>raw</tt> - makes no modifications to outgoing data, and calls your incoming data callback for each byte received. This adds more overhead since the engine needs to call your Luup function for every character, and makes your code complex. So, generally avoid using '<tt>raw</tt>' and let us add support for your protocol if you have a new one we don't yet support.<br />
<br />
Caution: the <protocol> tag can be either in the I_xxxx file or the D_xxxx file or both. If the latter, they must be identical.<br />
<br />
Hint: with verbose logging enabled - log lines starting with "52" are RX data, those with "51" are TX data.<br />
<br />
=== <ioPort> ===<br />
One way to have the device talk to another device on the internet. If you put a TCP port number here, an outgoing connection attempt is made automatically when the device initializes. (The remote IP address should be placed in the 'ip' box in the device's advanced configuration tab.) If you don't use this tag, you can use luup.io.open(..) instead.<br />
<br />
=== <handleChildren> ===<br />
If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like IR transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.<br />
<br />
Another example: Your plugin has say twenty dimmers, as children, hanging off the parent. The dimming and on/off actions are to be controlled by functions in the parent device, not by each child. Setting this flag to 1, allows the parent routines to be in control, as they should be.<br />
<br />
Note: this tag must be located in the description file, not in the implementation file. It will not work in the latter.<br />
<br />
=== <functions> ===<br />
Put here the Lua code for functions you want to be able to use in other places in your Lua code. You can also declare local variables here before the functions are described. Watch out for XML syntax. In particular, the > and >= comparison operators should be escaped.<br />
<br />
=== <files> ===<br />
As an alternative to the <functions> element, you can put your Lua implementation in a separate file containing pure Lua code and not worry about XML escapes. By convention, the file name should begin with L_ and end with .lua. as in <files>L_MyDevice.lua</files><br />
<br />
=== <startup> ===<br />
<br />
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag. <br />
<br />
=== <actionList> ===<br />
<br />
This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file. <br />
<br />
==== function declarations ====<br />
<br />
Whatever Lua code you create in the tags will be put inside a function automatically by the Luup engine, and your code will be passed variables that are relevant to whatever the code needs to do. For example, the code inside the 'run' tag is passed lul_device,lul_settings where lul_device is the id of the device the action was sent to, and lul_settings has the arguments to the UPnP action. See: [[Luup Declarations]] for details.<br />
<br />
There are several different nodes you can put within the "action" node. <br />
<br />
==== <run/job/incoming/timeout> ====<br />
<br />
Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want. <br />
<br />
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on. <br />
<br />
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time. <br />
<br />
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values:<br />
<br />
1) the status of the job<br />
<br />
2) how long to wait before the job times out in seconds.<br />
<br />
The status can be:<br />
*0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon. It means it's waiting to start. If you return this value your 'job' code will be run again in the 'timeout' seconds <br />
*2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon. This means the job failed. Your code won't be run again. <br />
*4=job_Done: In vera's UI a job in this state is displayed as a green icon. This means the job finished ok. Your code won't be run again. <br />
*5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon. This means the job is running and you're waiting for return data. Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag. If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run. <br />
<br />
So in the SendProntoCode job code for the I_GC100, we return 5,10 which means we're waiting for data and should wait up to 10 seconds. <br />
<br />
While a job is active, a UPnP control point will see the status of the job when it uses the GetStatus action for Vera. In Vera's web ui an active job is shown as an icon next to the device. While Luup is in the middle of executing the Lua code for your job, the status is reported to the control point as 1=job_InProgress. <br />
<br />
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the I/O port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not. <br />
<br />
The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup Lua extensions]] lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <incoming> ====<br />
<br />
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic I/O port. The Luup engine handles low-level I/O for these types of ports so you only need to call one of the [[Luup Lua extensions]] to send data on the port. <br />
<br />
This is the general purpose incoming data handler that the Luup engine will call whenever data comes in on an I/O Port. It has a node 'lua' which contains the Lua code that is called. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <ir> ====<br />
<br />
Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the [[Luup IO Device]] in the [[Luup Configuration File]]. Note that other formats besides pronto format can be used. Refer to the [[Luup_Declarations#<ir>|IR tag]]<br />
<br />
==Walkthrough to create a device==<br />
<br />
We have documented step-by-step the detailed process of creating a Luup interface, including everything that was done to debug, for Somfy blind control device (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]), which is a simple 1-way serial device (ie send data, but don't get any response) here: [[Luup_Somfy_Walkthrough]]<br />
<br />
No matter type of Luup plugin you'll be creating the procedure to create it is the same. This is true for Luup plugins which do not talk to any external hardware, such as a 'Weather' plugin to provide weather services, as well as plugins that talk to infrared devices like a TV, and serial/network devices.<br />
<br />
The first step is to create the UPnP device specification and service specification files. Whenever possible you will want to re-use existing service specification files, as explained in "Introduction to UPnP" [[Luup_Plugins#Introduction_to_UPnP here]]. And if your device is functionally the same as an existing UPnP Device type, like a light switch, you should re-use an existing UPnP Device Specification file also so that a UPnP Control Point will know how to control your device with modification. A list of all currently known UPnP Device and Service specification files is here: [[Luup_UPNP_Files]]. Find the existing UPnP device file that is most similar to the Luup plugin you are creating, and list all the services that device will implement. Download these files on your computer either from the [[Luup_UPNP_Files]] page, or by going to Vera's setup page and choosing Devices, Luup plugins.<br />
<br />
Modify the UPnP device specification file as needed, such as changing the manufacturer and model. Do not worry about the UDN tag as Luup will create a UDN for you automatically. If your device is functionally the same as the template you started from, leave the deviceType the same. If it's different, modify the deviceType replacing the "schemas-upnp-org" or "micasaverde-com" with some domain that you have, or if you don't have one, just use your name. Leave the :device: but change the word after it to describe your device. The deviceType must contain only a-z, 0-9, : and -.<br />
<br />
In the '''<serviceList>''' tag, add all the services you will implement. You can also create new services at this time. Save your new device file and service files as D_[some name] and S_[some name]. This is not mandated by UPnP, but is a convention we use so it's easy to recognize the file type by the name.<br />
<br />
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.<br />
<br />
To add the device to Vera's configuration file, so Vera will load and use the device, go to the Devices tab and at the bottom fill in the UPnP Device filename in the 'Add Device' box and pick a room. When you save your changes, which causes the Luup engine to reload the new configuration, it will look for the device, service and implementation files to start the device. If the files don't already exist on Vera, Vera will log an error and will not start the device. So you need to upload any new files you created by going to the Devices, Luup plugins page in Vera's setup web page. You can upload several files at once. The files will not be used until the Loop engine is reset so you probably want to check the "Restart Luup after upload" box before you click 'go'. If you upload the files without checking the box and want to restart the Luup engine, just click 'save', even if the button is grayed out. You will likely need to make several changes to the files before they're right so you can leave one web browser open to the 'Luup plugin' page and just modify the files in your text editor then click 'go' again to re-upload them after saving your changes. You can open another browser window or tab to access other pages in Vera's web ui and control the device while leaving the list of files to upload on the Luup plugin page intact so you can re-upload by clicking 'go'.<br />
<br />
Next you'll want to know how to debug your Luup plugins and Lua code. See [[Luup_Debugging]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugins:_Static_JSON_fileLuup plugins: Static JSON file2015-02-11T04:38:37Z<p>A-lurker: /* Root keys */</p>
<hr />
<div>[[Category:Development]]<br />
<br />
Starting in UI4, Luup plugins can specify a ''static JSON'' file. This file describes how the plugin appears in the web interface.<br />
<br />
=What the static JSON file controls=<br />
<br />
* The icon used by the plugin in the dashboard.<br />
* The text that displays in the one-to-two-row status message in the dashboard.<br />
* Whether the icon changes depending on the value of a variable in the device (for example, a binary light's icon changes from lit to unlit).<br />
* The tabs that appear in the device's detail dialog (when you click the wrench/spanner).<br />
* The content of these tabs.<br />
* The triggers or events that are available for the device in the "Triggers" or "Events" tag of a scene.<br />
<br />
=Referencing the static JSON file from the Device XML file=<br />
<br />
The device XML file (customarly '''D_PluginName.xml''') contains a reference to the static JSON file, in the '''staticJson''' element. Place the element as a child of the '''device''' element:<br />
<br />
<pre><nowiki><br />
<?xml version="1.0"?><br />
<root xmlns="urn:schemas-upnp-org:device-1-0"><br />
<specVersion><!-- ... --></specVersion><br />
<device><br />
<deviceType>urn:schemas-futzle-com:device:holidayvirtualswitch:1</deviceType><br />
<staticJson>D_HolidayVirtualSwitch1.json</staticJson><br />
<!-- ... --><br />
</nowiki></pre><br />
<br />
Capitalization is important, as with all XML.<br />
<br />
The static JSON file is customarily called '''D_PluginName.json'''.<br />
<br />
=The static JSON file=<br />
<br />
The static JSON file is a single JSON object (associative array).<br />
<br />
==Root keys==<br />
<br />
These keys have been seen at the top level in static JSON files in the wild:<br />
<br />
; flashicon<br />
: The device's icon, as a string. Despite the name, the icon is no longer related to Adobe Flash in UI4 or UI5. See [[Luup plugin icons]].<br />
; imgIconBody<br />
: Ignored in UI5.<br />
; imgIconDimmable<br />
: Ignored in UI5.<br />
; imgIconTurnable<br />
: Ignored in UI5.<br />
; imgIconMin<br />
: Ignored in UI5.<br />
; imgIconMax<br />
: Ignored in UI5.<br />
; halloIconsDir<br />
: Ignored in UI5.<br />
; inScene<br />
: When included and set equal to one, it enables any buttons, etc located on the "dashboard box", so they can be selected in the scene editor for use in scenes, rather than being grayed out.<br />
; DisplayStatus<br />
: A JSON object (associative array). For devices where the icon changes based on a variable's value, describes which variable, and the range of values that produce different icon images. See [[Luup plugin icons]].<br />
; state_icons<br />
: A JSON array. For devices where the icon changes based on a variable's value, describes which icon files exist. See [[Luup plugin icons]]. Used only in firmware 1.5.401 or later.<br />
; doc_url<br />
: A JSON object (associative array). In UI5, the only item in this object which is used is doc_page. It controls which page in docs5.mios.com is brought up when you click on the help ('''?''') icon in the device control panel.<br />
; Tabs<br />
: A JSON array. Describes the tabs in the detail dialog, and what subset of the first tab appears in the dashboard. See [[Luup plugin tabs]].<br />
; ToggleButton<br />
: ?????<br />
; DeviceType<br />
: A JSON string. Must match the '''deviceType''' element in the corresponding device XML ('''D_PluginName.xml''') file.<br />
; eventList<br />
: A JSON object (associative array). Describes the events that this plugin can produce. Events are triggers that can fire off actions in scenes. This key is required for UI4, and obsoleted in UI5. For UI5, use the eventList2 tag, which has a different format. For compatibility with both UI4 and UI5 you need the same information in both keys. See [[UI4 UI5 Migration]] for more information.<br />
; eventList2<br />
: see eventList, above.<br />
; sceneList<br />
: A JSON object (associative array). Describes the actions that this plugin can perform (for instance, as the action in a scene). This key is not required and is ignored in UI5, however it is needed for UI4 compatibility. See [[UI4 UI5 Migration]] for more information about its format.<br />
<br />
= UI7 Updates =<br />
<br />
* '''default_icon'''<br />
: replace the old "flashicon" from UI5. See [[Luup plugin icons]]<br />
* '''state_icons'''<br />
: state_icons mechanism has changed in UI7. Please see [[Luup plugin icons]]<br />
* '''TopNavigationTab''' = '''top_navigation_tab'''<br />
<br />
In device cpanel, tabs can be placed either in the top part or in the bottom. By default they are put in the bottom part. You can mark your tabs with a special flag so that they can be placed in top navigation bar of the cpanel.<br />
If two or more tabs are marked with ‘'''TabType=”flash”'''’, only the first one found will be placed in top navigation bar. If you want all of them to be placed there, you have to add ‘'''top_navigation_tab'''’ to each one.<br />
Let’s assume you have a plugin named ‘MyPlugin’ and you want to put a button in the top navigation bar, which, when clicked, will display a message in the cpanel.<br />
<br />
* Add the following lines to the .json device file (D_MyPlugin.json), in the Tabs section:<br />
<source lang="javascript"><br />
{<br />
"Label": {<br />
"lang_tag": "About",<br />
"text": "About"<br />
},<br />
"TopNavigationTab": "1",<br />
"Position": "4",<br />
"TabType": "javascript",<br />
"ScriptName": "J_MyPlugin.js",<br />
"Function": "MyPlugin.about"<br />
}<br />
</source><br />
<br />
Notice the property ‘'''TopNavigationTab'''’ set to the value of ‘1’. This is the line that specifies where to place the tab in cpanel. If you set a value different than 1, the tab will be placed in the bottom part of the cpanel, so always use ‘1’ if you want the tab in the top bar. You can also use ‘'''top_navigation_tab'''’.<br />
<br />
* Add the following lines of code to your .js file (J_MyPlugin.js):<br />
<br />
<source lang="javascript"><br />
var MyPlugin = (function(api){<br />
return {<br />
about: function() {<br />
try { <br />
var html = '<div>This is all about me !</div>';<br />
api.setCpanelContent(html);<br />
} catch (e) {<br />
Utils.logError('Error in MyPlugin.about(): ' + e);<br />
}<br />
}<br />
};<br />
})(api);<br />
</source><br />
<br />
* Perform a lu reload, enter device cpanel and click on ‘About’ button (which is in the top navigation bar of the cpanel) and the result will be something like this:<br />
<br />
[[File:TopNavigationTab.jpg|image|left]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-02-08T00:13:52Z<p>A-lurker: /* function: append */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters:<br />
* [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
* ptr (binary object)<br />
* id (string)<br />
* description (string)<br />
* device_type (string)<br />
* device_filename (string)<br />
* implementation_filename (string)<br />
* parameters (string)<br />
* embedded (boolean)<br />
* [invisible (boolean)] optional<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-02-08T00:08:08Z<p>A-lurker: /* function: append */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters:<br />
[[Luup_Lua_extensions#device:_string_or_number|device (string or number)]],<br />
* ptr (binary object)<br />
* id (string)<br />
* description (string)<br />
* device_type (string)<br />
* device_filename (string)<br />
* implementation_filename (string)<br />
* parameters (string)<br />
* embedded (boolean)<br />
* [invisible (boolean)] optional<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T09:21:29Z<p>A-lurker: /* Tab fields */</p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). One for each Tab. <br />
<br />
==Tab fields==<br />
These fields must be present for each tab JSON object in the tab array: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
These fields are optional for each tab JSON object in the tab array: <br />
<br />
;'''ControlGroup'''<br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below.<br />
<br />
;'''SceneGroup'''<br />
:Added in UI5.<br />
<br />
;'''Control'''<br />
:An array of controls for each tab. Each control consists of:<br />
*'''ControlType''' - the Control Types are described further below. <br />
*'''ControlGroup''' - a reference to the control's ControlGroup <br />
*'''text_align''' - align left or right <br />
*'''top''' - dashboard position in button units<br />
*'''left''' - dashboard position in button units<br />
<br />
eg a slider sitting above a two by two array of buttons:<br />
<br />
slider: top = 0, left = 0<br\><br />
top left button: top = 1.5, left = 0<br/><br />
top right button: top = 1.5, left = 1<br/><br />
bot left button: top = 2.5, left = 0<br/><br />
bot right button: top = 2.5, left = 1<br/><br />
<br />
===TabType javascript===<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
===TabType flash===<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''SceneGroup'''<br />
<br />
''SceneGroup'' (added in UI5) is similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the Scene Editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the Scene Editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them.<br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T08:46:21Z<p>A-lurker: /* Tab fields */</p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). One for each Tab. <br />
<br />
==Tab fields==<br />
These fields must be present for each tab JSON object in the tab array: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
These fields are optional for each tab JSON object in the tab array: <br />
<br />
;'''ControlGroup'''<br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below.<br />
<br />
;'''SceneGroup'''<br />
:Added in UI5.<br />
<br />
;'''Control'''<br />
:An array of controls for each tab. Each control consists of:<br />
*'''ControlType''' - the Control Types are described further below. <br />
*'''ControlGroup''' - a reference to the control's ControlGroup <br />
*'''text_align''' - align left or right <br />
*'''top''' - dashboard position in button units<br />
*'''left''' - dashboard position in button units<br />
*'''x''' - does what? <br />
*'''y''' - does what?<br />
<br />
eg a slider sitting above a two by two array of buttons:<br />
<br />
slider: top = 0, left = 0<br\><br />
top left button: top = 1.5, left = 0<br/><br />
top right button: top = 1.5, left = 1<br/><br />
bot left button: top = 2.5, left = 0<br/><br />
bot right button: top = 2.5, left = 1<br/><br />
<br />
===TabType javascript===<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
===TabType flash===<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''SceneGroup'''<br />
<br />
''SceneGroup'' (added in UI5) is similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the Scene Editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the Scene Editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them.<br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T08:03:50Z<p>A-lurker: /* Tab fields */</p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). One for each Tab. <br />
<br />
==Tab fields==<br />
These fields must be present for each tab JSON object in the tab array: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
These fields are optional for each tab JSON object in the tab array: <br />
<br />
;'''ControlGroup'''<br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below.<br />
<br />
;'''SceneGroup'''<br />
:Added in UI5.<br />
<br />
;'''Control'''<br />
:An array of controls for each tab. Each control consists of:<br />
*'''ControlGroup''' - a reference to the control's ControlGroup <br />
*'''ControlType''' - the Control Types are described further below. <br />
*'''text_align''' - align left or right <br />
*'''top''' - does what? <br />
*'''left''' - does what?<br />
*'''x''' - does what? <br />
*'''y''' - does what?<br />
<br />
===TabType javascript===<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
===TabType flash===<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''SceneGroup'''<br />
<br />
''SceneGroup'' (added in UI5) is similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the Scene Editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the Scene Editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them.<br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T07:44:41Z<p>A-lurker: </p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). One for each Tab. <br />
<br />
==Tab fields==<br />
These fields must be present for each tab JSON object in the tab array: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
These fields are optional for each tab JSON object in the tab array: <br />
<br />
;'''ControlGroup'''<br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below.<br />
<br />
;'''SceneGroup'''<br />
:Added in UI5.<br />
<br />
;'''Control'''<br />
:An array of controls for each tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's ControlGroup <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
===TabType javascript===<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
===TabType flash===<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''SceneGroup'''<br />
<br />
''SceneGroup'' (added in UI5) is similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the Scene Editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the Scene Editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T07:43:42Z<p>A-lurker: </p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). One for each Tab. <br />
<br />
==Tab fields==<br />
These fields must be present for each tab JSON object in the tab array: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
These fields are optional for each tab JSON object in the tab array: <br />
<br />
;'''ControlGroup'''<br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below.<br />
<br />
;'''SceneGroup'''<br />
:Added in UI5.<br />
<br />
;'''Control'''<br />
:An array of controls for each tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's ControlGroup <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
===TabType javascript===<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
===TabType flash===<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' (added in UI5) is similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the Scene Editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the Scene Editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
==Control Groups==<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T07:37:04Z<p>A-lurker: </p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). One for each Tab. <br />
<br />
==Tab fields==<br />
These fields must be present for each tab JSON object in the tab array: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
These fields are optional for each tab JSON object in the tab array: <br />
<br />
;'''ControlGroup'''<br />
<br />
;'''SceneGroup'''<br />
:Added in UI5.<br />
<br />
;'''Control'''<br />
:An array of controls for each tab.<br />
<br />
===TabType javascript===<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
===TabType flash===<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' (added in UI5) is similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the Scene Editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the Scene Editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
;'''ControlGroup''' <br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below. <br />
;'''Control''' <br />
:An array of JSON objects, each representing a control on the tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's control group <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
==Control Groups==<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T07:21:27Z<p>A-lurker: </p>
<hr />
<div>=Root Keys=<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
=Tabs Key=<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). <br />
<br />
==Required tab fields==<br />
These fields must be present in each JSON object in the tab list: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys:<br />
*'''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs.<br />
*'''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
==Control Groups==<br />
==TabType javascript==<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
==TabType flash==<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' is a new type of container, similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the scene editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the scene editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
;'''ControlGroup''' <br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below. <br />
;'''Control''' <br />
:An array of JSON objects, each representing a control on the tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's control group <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
===Dashboard appearance===<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
===Making a control appear on the dashboard===<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
===Keeping controls together===<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
==Control Types==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
===ControlType label===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
===ControlType variable===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
===ControlType input===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
===ControlType button===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
===ControlType slider===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
===ControlType slider_vertical===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
===ControlType checkbox===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
===ControlType image===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType image_player===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
===ControlType js_button===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=UI7 Updates=<br />
<br />
===ControlType multi_state_button=== <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T07:05:52Z<p>A-lurker: </p>
<hr />
<div>== Root Keys ==<br />
Refer to [[Luup_plugins:_Static_JSON_file|Root Keys]].<br />
<br />
== Tabs Key ==<br />
The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears on the '''Dashboard''' and '''Devices''' web pages.<br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). <br />
<br />
= Required tab fields =<br />
<br />
These fields must be present in each JSON object in the tab list: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys: '''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs; '''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
== Control Groups ==<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
== Making a control appear on the dashboard ==<br />
<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
== Keeping controls together ==<br />
<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
== TabType javascript ==<br />
<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
== TabType flash ==<br />
<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' is a new type of container, similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the scene editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the scene editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
;'''ControlGroup''' <br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below. <br />
;'''Control''' <br />
:An array of JSON objects, each representing a control on the tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's control group <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
== Control Types ==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
=== ControlType label ===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
=== ControlType variable ===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
=== ControlType input ===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
=== ControlType button ===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
=== ControlType slider ===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
=== ControlType slider_vertical ===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
=== ControlType checkbox ===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
=== ControlType image ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType image_player ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType js_button ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
= Dashboard appearance =<br />
<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
= UI7 Updates =<br />
<br />
=== ControlType multi_state_button === <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T06:14:43Z<p>A-lurker: /* TabType flash */</p>
<hr />
<div><br> The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears in the dashboard. <br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). <br />
<br />
= Required tab fields =<br />
<br />
These fields must be present in each JSON object in the tab list: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys: '''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs; '''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
== TabType javascript ==<br />
<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
== TabType flash ==<br />
<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' is a new type of container, similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the scene editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the scene editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
;'''ControlGroup''' <br />
:Required only if the first tab is also used to display elements on the dashboard. See [[Luup_plugin_tabs#Dashboard_appearance|''Dashboard appearance'']] below. <br />
;'''Control''' <br />
:An array of JSON objects, each representing a control on the tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's control group <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
== Control Types ==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
=== ControlType label ===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
=== ControlType variable ===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
=== ControlType input ===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
=== ControlType button ===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
=== ControlType slider ===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
=== ControlType slider_vertical ===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
=== ControlType checkbox ===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
=== ControlType image ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType image_player ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType js_button ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
= Dashboard appearance =<br />
<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
== Control Groups ==<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
== Making a control appear on the dashboard ==<br />
<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
== Keeping controls together ==<br />
<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
[[Category:Development]]<br />
<br />
= UI7 Updates =<br />
<br />
=== ControlType multi_state_button === <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T05:58:20Z<p>A-lurker: /* ControlType label */</p>
<hr />
<div><br> The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears in the dashboard. <br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). <br />
<br />
= Required tab fields =<br />
<br />
These fields must be present in each JSON object in the tab list: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys: '''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs; '''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
== TabType javascript ==<br />
<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
== TabType flash ==<br />
<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' is a new type of container, similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the scene editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the scene editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
;'''ControlGroup''' <br />
:Required only if the first tab is also used to display elements on the dashboard. See ''Dashboard appearance'' below. <br />
;'''Control''' <br />
:An array of JSON objects, each representing a control on the tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's control group <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
== Control Types ==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
=== ControlType label ===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab. The label is left aligned.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
=== ControlType variable ===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
=== ControlType input ===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
=== ControlType button ===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
=== ControlType slider ===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
=== ControlType slider_vertical ===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
=== ControlType checkbox ===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
=== ControlType image ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType image_player ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType js_button ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
= Dashboard appearance =<br />
<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
== Control Groups ==<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
== Making a control appear on the dashboard ==<br />
<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
== Keeping controls together ==<br />
<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
[[Category:Development]]<br />
<br />
= UI7 Updates =<br />
<br />
=== ControlType multi_state_button === <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_plugin_tabsLuup plugin tabs2015-01-26T05:40:30Z<p>A-lurker: /* ControlType label */</p>
<hr />
<div><br> The '''Tabs''' key in the [[Luup plugins: Static JSON file|static JSON file]] performs two duties in the UI4 HTML interface: <br />
<br />
*It controls how the device appears in the dashboard. <br />
*It controls how each tab appears in the the device's detailed dialog.<br />
<br />
The value of the '''Tabs''' key is a JSON array. Each array element is a JSON object (associative array). <br />
<br />
= Required tab fields =<br />
<br />
These fields must be present in each JSON object in the tab list: <br />
<br />
;'''Label''' <br />
:A JSON object containing two keys: '''lang_tag''' (a string) is not displayed in the HTML interface but may be used by localized UIs; '''text''' (a string) is displayed in the tab's handle at the top of the dialog. <br />
;'''Position''' <br />
:A string which contains a number matching the relative position of this tab in relation to the other tabs. The leftmost tab has value "0", the next tab "1", and so on. <br />
;'''TabType''' <br />
:A string which describes how the content of the tab is to be generated. The value may be one of '''javascript''' or '''flash'''. These are described in the following sections.<br />
<br />
== TabType javascript ==<br />
<br />
The '''javascript''' tab type executes client-side JavaScript, producing an HTML fragment which forms the body of the tab. These additional keys are required in the tab JSON object: <br />
<br />
;'''ScriptName''' <br />
:The name of the JavaScript file. Conventionally, plugin JavaScript files are named '''J_PluginName.js'''. <br />
;'''Function''' <br />
:The function inside the JavaScript file which will be called to produce the HTML.<br />
<br />
The JavaScript code has access to most Luup variables through the [[JavaScript API]]. <br />
<br />
== TabType flash ==<br />
<br />
The '''flash''' tab type creates a tab body with a simple page-description language. Each component of the page is placed into the body at the position specified. You can't place a '''Control''' on the dashboard unless you place it in a '''ControlGroup''', which in turn must be placed in a '''SceneGroup''': <br />
<br />
;'''UI5 SceneGroup'''<br />
<br />
''SceneGroup'' is a new type of container, similar to a ControlGroup, but with some key differences: it is customizable (by setting its position and dimensions) and it defines the behavior of the buttons assigned to the same state variable. If two buttons in the same SceneGroup are assigned to the same variable, only one of them can be activated in the scene editor (radio buttons behavior). If two buttons assigned to the same variable are in different SceneGroups, both can be activated at the same time in the scene editor (checkboxes behavior). <br />
<br />
It has the following tags: <br />
<br />
*'''id''': A numeric value (positive integer numbers). <br />
*'''top''': The offset from the device header. It uses the same units as ''x'' and ''y''. <br />
*'''left''': The offset from the device icon. It uses the same units as ''x'' and ''y''. <br />
*'''x''', '''y''': the dimensions of the SceneGroup. e.g If I have two horizontally aligned buttons in the SceneGroup, ''x'' will be ''2'' and ''y'' will be ''1''.<br />
<br />
A ''SceneGroup'' can contain one or more ''ControlGroups''. <br />
<br />
;'''ControlGroup''' <br />
:Required only if the first tab is also used to display elements on the dashboard. See ''Dashboard appearance'' below. <br />
;'''Control''' <br />
:An array of JSON objects, each representing a control on the tab. The Control Types are described further below. Each control object in the array can also contain: <br />
:'''ControlGroup''' - a reference to the control's control group <br />
:'''top''' - does what? <br />
:'''left''' - does what?<br />
<br />
== Control Types ==<br />
<br />
The '''Control''' array contains JSON objects (associative arrays), one per item in the tab body. An item is either a static text label, a variable, an input field, or a button or slider. This is handled by setting the '''ControlType''' key on the object. <br />
<br />
=== ControlType label ===<br />
<br />
With '''ControlType''' set to '''label''', a fixed label is placed at the specified coordinates. The following additional keys must also be provided: <br />
;'''Label''' <br />
:A JSON object with two keys:<br />
*'''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. The key is optional.<br />
*'''text''' is displayed as the content of the label. In UI5 on the "Dashboard", the text is rendered by passing it in the "Title" attribute of a Div statement, so it can only be plain text, (as opposed to say HTML). However in the tabs, it is placed in a Div statement so HTML, such as a URL link, can be rendered. <br />
<br />
;'''Display''' <br />
:A JSON object with four keys, which are strings containing numbers (in pixels?). This controls where the label is placed on the tab.<br />
*'''Top''': The offset from the top of tab writable area.<br />
*'''Left''': The offset from the left side of the tab writable area.<br />
*'''Width''': Window width of the label.<br />
*'''Height''': Window height of the label.<br />
<br />
=== ControlType variable ===<br />
<br />
With '''ControlType''' set to '''variable''', the contents of a Luup variable are displayed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with six keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the variable's value is placed in the tab), '''Service''' (a string containing the Service Id of the variable to be displayed), and '''Variable''' (a string containing the name of the variable to be displayed).<br />
<br />
=== ControlType input ===<br />
<br />
With '''ControlType''' set to '''input''', a text box for the user to type a string is placed in the tab. The following additional keys must also be provided: <br />
<br />
;'''ID''' <br />
:A string which other controls (buttons) will use to refer to the string that the user has typed into this field. <br />
;'''Display''' <br />
:A JSON object with four keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers). This controls where the text box is placed in the tab.<br />
<br />
=== ControlType button ===<br />
<br />
With '''ControlType''' set to '''button''', a clickable button is placed at the specified coordinates. The button will appear selected (depressed) or unselected based on the value of a variable, permitting sets of buttons to act as radio buttons. The following additional keys must also be provided: <br />
<br />
;'''Label''' <br />
:A JSON object with two keys: '''lang_tag''' is not displayed but can be used by localizing interfaces to translate the label text. '''text''' is displayed as the text inside the button. <br />
;'''Display''' <br />
:A JSON object with up to seven keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), and (optionally) '''Value''' (a string, "1" if omitted). The specified variable's value controls whether the button appears selected (if the variable matches '''Value''') or not selected (if the variable doesn't match). <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching an input control on the page (for a user-entered parameter value).<br />
<br />
=== ControlType slider ===<br />
<br />
With '''ControlType''' set to '''slider''', a draggable slider is placed at the specified coordinates. The following additional keys must also be provided: <br />
<br />
;'''Display''' <br />
:A JSON object with nine keys: '''Top''', '''Left''', '''Width''', '''Height''' (all strings containing numbers, controlling where the button is placed in the tab), '''Service''' (a string containing the Service Id of a variable), and '''Variable''' (a string containing the name of a variable), '''MinValue''' and '''MaxValue''' (both strings containing numbers, controlling what range of values the slider takes) and '''ID''' (a string, used in the '''Command''' to refer to the position the user has dragged the slider to). The specified variable's value is used to display the slider's current position. <br />
;'''Command''' <br />
:A JSON object with three keys: '''Service''' (a string containing the service Id of an action that this device can perform), '''Action''' (a string containing the name of the action), and '''Parameters''' (a JSON array of JSON objects, forming the parameters to pass to the action). Each parameter object has a key '''Name''' for the parameter name, and either a key '''Value''' (for a fixed parameter value) or a key '''ID''' matching the slider's ID (for a value matching what the user has dragged the slider to). <br />
;'''ControlHeader''' <br />
:Optional. If present, must have string value "1". When set, the slider's label and value are displayed. Otherwise only the slider is shown. (TODO: This is from reverse-engineering the JavaScript code. Test this.)<br />
<br />
=== ControlType slider_vertical ===<br />
<br />
:Used in: D_Heater1.json, D_HVAC_ZoneThermostat1.json<br />
<br />
=== ControlType checkbox ===<br />
<br />
:As of UI5 firmware 1.5.622, checkboxes are only half-baked and not usable in "flash" style tabs. If you need check-boxes, radio buttons, pop-up menus, etc. in your tab, you need to create a Javascript tab.<br />
<br />
=== ControlType image ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType image_player ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
=== ControlType js_button ===<br />
<br />
:Used in: D_DigitalSecurityCamera1.json<br />
<br />
= Dashboard appearance =<br />
<br />
The first array element in the '''Tabs''' field is special. If it is of type '''flash''' then a subset of the controls on the tab can be chosen to display on the UI4 dashboard.<br />
<br />
== Control Groups ==<br />
<br />
The dashboard box for a device has room for two rows of information and controls. These are called ''Control Groups''.<br />
<br />
The tab object must contain the key '''ControlGroup''', which specifies how the dashboard's two available rows of information are to be handled. '''ControlGroup''' is a JSON array containing JSON objects (associative arrays). Each object has the following keys:<br />
<br />
;'''id''' <br />
:A string containing a number, "1" for the first object, "2" for the second, and so on. <br />
;'''isSingle''' <br />
:UI4: If this key is present and contains the value "1", then this control group is combined with other control groups that have '''isSingle''' similarly set onto the same row. For an example of this, see the On/Off buttons of the Binary Switch and Dimmable Switch static JSON files. <br />
:UI5: Since controls cannot be grouped in drop-down lists anymore, this tag has a different role in UI5. When isSingle is "1", the buttons in the ControlGroup will have a background (e.g. the BinaryLight buttons). If isSingle is missing, the buttons won't have a background. That is the button shape is not shown; only the button text will show. <br />
;'''type''' <br />
:If this key is present and contains the value "info", then the controls displayed on this row are not clickable. If this key is absent, buttons may be used in the control group.<br />
<br />
== Making a control appear on the dashboard ==<br />
<br />
All control types (label, variable, button, input, slider) can appear in the dashboard. However, by default, controls in the first tab's detail view do not appear in the dashboard. To make a control's text appear in the dashboard, add a '''ControlGroup''' to the control's JSON object. The value is a string containing a number, matching the '''id''' of one of the Tab's overall '''ControlGroup''' key. The dashboard will concatenate all of a control group's controls in the order they are presented in the '''Control''' array, and display them in a single row of text. <br />
<br />
== Keeping controls together ==<br />
<br />
Some controls may not make sense if presented individually (for instance, an input for an alarm PIN and a "disarm" button). To keep these controls together, add a '''ControlPair''' key to all of the controls that have to appear together. Use the same value (a number in a string) for all of them. <br />
<br />
[[Category:Development]]<br />
<br />
= UI7 Updates =<br />
<br />
=== ControlType multi_state_button === <br />
This is a new type of control in UI7 and works as a “toggle” button. It is used for On/Off, Armed/Disarmed etc buttons.<br />
<br />
As an example, here is the definition from the .json file of the Door/Window sensor needed to create this kind of control:<br />
<source lang="javascript"><br />
{<br />
"ControlGroup": "1",<br />
"ControlType": "multi_state_button",<br />
"top": "0",<br />
"left": "1",<br />
"states": [<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_arm",<br />
"text": "Arm"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "1"<br />
},<br />
"Command": {<br />
“Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "1"<br />
}<br />
]<br />
},<br />
"ControlCode": "arm"<br />
},<br />
{<br />
"Label": {<br />
"lang_tag": "ui7_cmd_bypass",<br />
"text": "Bypass"<br />
},<br />
"ControlGroup": "1",<br />
"Display": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Variable": "Armed",<br />
"Value": "0"<br />
},<br />
"Command": {<br />
"Service": "urn:micasaverde-com:serviceId:SecuritySensor1",<br />
"Action": "SetArmed",<br />
"Parameters": [<br />
{<br />
"Name": "newArmedValue",<br />
"Value": "0"<br />
}<br />
]<br />
},<br />
"ControlCode": "bypass"<br />
}<br />
]<br />
}<br />
</source><br />
<br />
* '''ControlGroup''' - an integer - has the same behaviour as in previous UIs <br />
* '''ControlType''' - a string which must be set to ‘multi_state_button’<br />
* '''top''' - an integer specifying the row from the control group where the control will be placed<br />
* '''left''' - an integer which specifies which column will the control be placed in<br />
* '''states''' - an array of objects, each object representing a different state for the button; each object has the following properties:<br />
** '''Lable''' - a JSON object with two keys:<br />
*** '''lang_tag''' - a string used for localization<br />
*** '''text''' - the string displayed if localization fails<br />
** '''Display''' - a JSON object with the following keys:<br />
*** '''Service''' - a string containing the service id<br />
*** '''Variable''' - a string containing the name of the variable<br />
*** '''Value''' - a number which represents the value of the state in which the control must be in order for this state to be displayed/marked as selected<br />
** '''Command''' - a JSON object with the following properties:<br />
*** '''Service''' - a string with the service id of the action which this state will perform<br />
*** '''Action''' - a string representing the name of the action to be executed<br />
** '''Parameters''' - an array of JSON objects holding the parameters to pass to the action<br />
* '''ControlCode''' - a string containing the control code of this control (it is recommended to be unique for each device type)<br />
<br />
As you can see, the multi_state_button is a merge resulted from two controls of type button.<br />
<br />
[[File:MotionSensor.jpg|image|left]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-01-26T05:29:26Z<p>A-lurker: /* function: write */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-01-26T05:24:25Z<p>A-lurker: /* function: open */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (as number or string), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device id (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-01-26T05:23:46Z<p>A-lurker: /* function: open */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
There is no 'function: close'.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device id (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-01-26T05:22:30Z<p>A-lurker: /* function: is_connected */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device id (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false. Unplugging the LAN cable associated with the port, will not set the flag to false.<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2015-01-26T05:20:35Z<p>A-lurker: /* function: write */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing.<br> <br />
<br />
[[Example usage]]<br />
<source lang="lua">local value, tstamp = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value .. " last changed (Epoch): " .. tstamp)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
[[Caution - Incorrect usages]]<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tonumber</code> also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by <code>luup.variable_get</code> is being used as a number base in the function <code>tonumber</code>. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
<code>luup.variable_get</code> returns two parameters and <code>tostring</code> only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: job_watch ===<br />
<br />
parameters: function_name (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever a job is created, finished, or changes state then ''function_name'' will be called. If the device is nil or not specified, ''function_name'' will be called for all jobs, otherwise only for jobs that involve the specified device.<br />
<br />
Example:<br />
<br />
luup.job_watch("mycallback")<br />
luup.job_watch("mycallback",6)<br />
<br />
The first one registers a callback for all devices, the second one only for device 6. Note that multiple registrations will result in multiple callbacks, so two calls like that means that "mycallback" would be called once for all devices except 6, and for 6 it would be called twice.<br />
<br />
The callback function will be passed a table which contains:<br />
<br />
device_num: the number of the device<br />
<br />
status: the job status, 0-7 as follows<br />
<br />
WaitingToStart=0<br />
InProgress=1<br />
Error=2<br />
Aborted=3<br />
Done=4<br />
WaitingForCallback=5<br />
Requeue=6<br />
InProgressPendingData=7<br />
<br />
name: the name of the job<br />
<br />
type: the C++ class name for the type of job<br />
<br />
notes: any notes or progress that set for the job<br />
<br />
Here is an example of the callback:<br />
<br />
function mycallback(lul_job)<br />
luup.log("mycallback device #" .. lul_job.device_num .. " status " .. lul_job.status .. " name " .. lul_job.name .. " type " .. lul_job.type .. " notes " .. lul_job.notes);<br />
end<br />
<br />
and here is the output in LuaUPnP.log from the above function when it is registered to watch a ZWave device which was turned ON:<br />
<br />
50 07/09/14 17:43:43.491 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes <00E998D0><br />
50 07/09/14 17:43:43.492 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes <00E93328><br />
50 07/09/14 17:43:43.493 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.520 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.543 luup_log:3: mycallback device #6 status 0 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E92A58><br />
50 07/09/14 17:43:43.544 luup_log:3: mycallback device #6 status 1 name ON node 2 type ZWJob_SendData notes Waiting to send again with ack <00E93328><br />
50 07/09/14 17:43:43.545 luup_log:3: mycallback device #6 status 7 name ON node 2 type ZWJob_SendData notes Sending the Z-Wave command after 0 retries <00E93328><br />
50 07/09/14 17:43:43.576 luup_log:3: mycallback device #6 status 5 name ON node 2 type ZWJob_SendData notes Waiting for node to reply after 0 retries <00E93328><br />
50 07/09/14 17:43:43.631 luup_log:3: mycallback device #6 status 4 name ON node 2 type ZWJob_SendData notes Transmit was ok <00E92A58><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the <tt>uup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as ''urn:schemas-upnp-org:device:BinaryLight:1''.<br />
<br />
<span style="color: red">'''NOTE:''' On UI7, the <tt>device_type</tt> '''MUST''' be either the '''empty string''', or '''the same''' as the one in the device file, otherwise the Luup engine will restart continuously.</span><br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the XML file with the UPnP device specification. If the <tt>device_file</tt> contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>. The <tt>deviceType</tt> from the filename will override any <tt>device_type</tt> you set manually ''('''NOTE''': This applies only for UI5 and older UIs.)''.<br />
<br />
If <tt>embedded</tt> is true, the ''embedded'' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|optional device id (string or number)]] <br />
<br />
returns: result (boolean or nil) <br />
<br />
The device id defaults to self, if omitted. In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nil if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_RequestsLuup Requests2014-03-15T22:34:24Z<p>A-lurker: /* action */</p>
<hr />
<div>[[Category:Development]]<br />
In addition to sending requests using standard UPnP, you can also do most things using a simple HTTP requests. Use the built-in URL <tt>data_request</tt>, and pass the following on the URL:<br />
*<tt>id</tt> the id of the request (prior to 15 Oct 2010 all requests had an lu_ in front, which is now optional),<br />
*<tt>output_format</tt> the format in which you want a response as <tt>json</tt>, <tt>xml</tt> or <tt>text</tt>.<br />
Not all requests support all <tt>output_format</tt> options.<br />
<br />
Here is the list of requests:<br />
<br />
==user_data==<br />
<br />
Example: http://ip_address:3480/data_request?id=user_data&output_format=xml<br />
<br />
This returns the configuration data for Vera, which is a list of all devices and the UPnP variables which are persisted between resets as well as rooms, names, and other data the user sets as part of the configuration.<br />
<br />
==status==<br />
This returns the current status for all devices including all the current UPnP variables and the status of any active jobs.<br />
<br />
<br />
Examples:<br />
* http://ip_address:3480/data_request?id=status&output_format=xml<br />
Or for a specific device:<br />
* http://ip_address:3480/data_request?id=status&output_format=xml&UDN=uuid:4d494342-5342-5645-0002-000000000002<br />
* http://ip_address:3480/data_request?id=status&output_format=xml&DeviceNum=6<br />
<br />
==sdata==<br />
<br />
This is an abbreviated form of [[#user_data|user_data]] and [[#status|status]] (sdata = ''summary data''). It allows a user interface that is only worried about control, and not detailed configuration, to get a summary of the data that would normally be presented to the user and to monitor the changes. See [[UI_Simple]] for a walkthrough.<br />
<br />
<br />
Examples:<br />
* http://ip_address:3480/data_request?id=sdata<br />
* http://ip_address:3480/data_request?id=sdata&output_format=xml<br />
<br />
==actions==<br />
<br />
==device==<br />
<br />
Example: http://ip_address:3480/data_request?id=device&action=rename&device=5&name=Chandalier&room=3<br />
<br />
Example: http://ip_address:3480/data_request?id=device&action=rename&device=5&name=Chandalier&room=Garage<br />
<br />
Example: http://ip_address:3480/data_request?id=device&action=delete&device=5<br />
<br />
This renames or deletes a device. Use action=rename or action=delete. For rename, you can optionally assign a room by passing either the ID or the name.<br />
<br />
<br />
<br />
---old---<br />
This returns all the XML with all the UPNP device description documents. Use: http://ip_address:3480/data_request?id=device&output_format=xml&DeviceNum=x or &UDN=y to narrow it down. Then when you see the service URL's, like <SCPDURL>/luvd/S_HVAC_UserOperatingMode1.xml</SCPDURL>, you can view them with: http://ip_address:3480/luvd/S_HVAC_UserOperatingMode1.xml<br />
---end old---<br />
<br />
==scene==<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=record<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=pause&seconds=y<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=stoprecord<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=listrecord<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=deleterecord&number=x<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=saverecord&name=whatever&room=X<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=rename&scene=5&name=Chandalier&room=Garage<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=delete&scene=5<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=create&json=[valid json data]<br />
<br />
Example: http://ip_address:3480/data_request?id=scene&action=list&scene=5<br />
<br />
This creates, renames, or deletes a scene depending on the action.<br />
<br />
Recording a scene means whatever actions come in after sending the 'record' will be saved into an internal buffer. listrecord shows what's recorded so far. pause adds a pause. deleterecord deletes some action in the internal buffer. When deleting, for number=x, use the same 'id' in listrecord. saverecord takes the internal buffer, the recorded macro, and saves it as an actual scene.<br />
<br />
To create a scene by hand, rather than recording it, use 'create'. When using the 'create' command json must be valid JSON for a scene as documented in [[Scene_Syntax]]. The name, room and optional id (if you're overwriting an existing scene) are passed in the json, so nothing is on the command line except the json. Because the json data can be long it is recommended to send it as an http POST instead of GET with the data passed with the name "json"<br />
<br />
list returns the JSON data for an existing scene.<br />
<br />
==room==<br />
<br />
Example: http://ip_address:3480/data_request?id=room&action=create&name=Kitchen<br />
<br />
Example: http://ip_address:3480/data_request?id=room&action=rename&room=5&name=Garage<br />
<br />
Example: http://ip_address:3480/data_request?id=room&action=delete&room=5<br />
<br />
This creates, renames, or deletes a room depending on the action. To rename or delete a room you must pass the room id for the room=.<br />
<br />
==file==<br />
Returns the contents of a file in '''/etc/cmh''' or '''/etc/cmh-ludl'''. Has one parameter, <tt>parameters</tt>, which is the name of the file.<br />
<br />
Example: http://ip_address:3480/data_request?id=file&parameters=D_BinaryLight1.xml<br />
<br />
==lua==<br />
<br />
== action ==<br />
<br />
Sends a UPnP action.<br />
<br />
'''Examples:'''<br />
<br />
* Turn off a light:<br />
http://ip_address:3480/data_request?id=action&amp;output_format=xml&amp;DeviceNum=6&amp;serviceId=urn:upnp-org:serviceId:SwitchPower1&amp;action=SetTarget&amp;newTargetValue=1 <br />
<br />
* Set a dimmable light to 30%:<br />
http://ip_address:3480/data_request?id=action&amp;output_format=json&amp;DeviceNum=7&amp;serviceId=urn:upnp-org:serviceId:Dimming1&amp;action=SetLoadLevelTarget&amp;newLoadlevelTarget=30 <br />
<br />
* Run a scene:<br />
http://ip_address:3480/data_request?id=action&amp;serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&amp;action=RunScene&amp;SceneNum=&lt;SceneNum&gt;<br />
<br />
* Install a plugin:<br />
http://ip_address:3480/data_request?id=action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=CreatePlugin&PluginNum=<PluginNum><br />
<br />
==variableset==<br />
<br />
http://ip_address:3480/data_request?id=variableset&DeviceNum=6&serviceId=urn:micasaverde-com:serviceId:DoorLock1&Variable=Status&Value=1<br />
<br />
If you leave off the DeviceNum and serviceID, then this sets a top-level json tag called "Variable" with the value.<br />
<br />
==variableget==<br />
<br />
http://ip_address:3480/data_request?id=variableget&DeviceNum=6&serviceId=urn:micasaverde-com:serviceId:DoorLock1&Variable=Status<br />
<br />
If you leave off the DeviceNum and serviceID, then this gets a top-level json tag called "Variable".<br />
<br />
==reload==<br />
<br />
Resets the Luup engine with any new configuration settings.<br />
<br />
Example: http://ip_address:3480/data_request?id=reload<br />
<br />
==alive==<br />
<br />
Return OK if the engine is running.<br />
<br />
==finddevice==<br />
<br />
Returns the '''device number''', '''UDN''' and '''device type''' of the first device that matches any or all the parameters passed on the URL: <tt>devtype</tt>, <tt>ip</tt>, <tt>devid</tt>, where <tt>devid</tt> is the ''altid'' of the device. Or instead of <tt>devid</tt>, pass '''<tt>devnum</tt>''' and it will find the device with that number.<br />
<br />
Example: http://ip_address:3480/data_request?id=finddevice&devid=6<br />
<br />
==resync==<br />
<br />
ReSync's all the devices, rooms, users, sections with event servers and returns OK<br />
<br />
==wget==<br />
<br />
Returns the contents of the URL you pass in the "url" argument. Optionally append "user" and "pass" arguments for http authentication, and "timeout" to specify the maximum time to wait in seconds.<br />
<br />
==iprequests==<br />
<br />
Returns the recent IP requests in order by most recent first, including information about devices in use and if the IP is blacklisted (ignored by the plug and play mechanism). Optionally append '''<tt>timeout</tt>''' to specify the oldest IP request in seconds.<br />
<br />
Examples:<br />
* This returns the devices which showed up on the network in the past hour:<br />
http://ip_address:3480/data_request?id=iprequests&timeout=3600<br />
* This returns the devices which showed up on the network in the last 24 hours:<br />
http://ip_address:3480/data_request?id=iprequests&timeout=86400<br />
<br />
==blacklistip==<br />
<br />
Append "ip" to the URL, and optionally "remove=1" to add (or remove) the IP to the blacklist so plug and play IP devices won't be added.<br />
<br />
<span style="color: rgb(255, 0, 0);">Not implemented yet!</span><br />
<br />
==live_energy_usage==<br />
<br />
For backward compatibility, it reports the current energy usage in a tab delimited format.<br />
<br />
Example: http://ip_address:3480/data_request?id=live_energy_usage<br />
<br />
'''Device #''' '''Name''' '''Room''' '''Category''' '''Watts'''<br />
59 Basement Light Basement 3 20<br />
60 Bedroom Light 1st Floor 3 0<br />
61 Porch Light Outside 3 30<br />
<br />
To display only the watts variable : http://ip_address:3480/data_request?id=variableget&DeviceNum=34&serviceId=urn:micasaverde-com:serviceId:EnergyMetering1&Variable=Watts<br />
and you must specify the device id for the variable and the service id.<br />
<br />
==request_image==<br />
<br />
Returns an image from a camera. This fetches the image from the camera using the URL variable for the device. Pass arguments:<br />
<br />
cam = the device id of the camera. This is the only mandatory argument.<br />
res = optional: a resolution, which gets appended to the variable. So passing "low" means the image from the URL_low variable will be returned. If it doesn't exist it reverts to the standard URL or DirectStreamingURL<br />
timeout = optional: how long to wait for the image to be retrieved, or how long to retrieve video. defaults to 10 seconds.<br />
url = optional: override the camera's default URL<br />
ip = optional: override the camera's default ip<br />
user or pass = optional: override the camera's default username/password<br />
<br />
==archive_video==<br />
''* UI5+ only''<br />
<br />
Archives a MJPEG video or a JPEG snapshot.<br />
<br />
Parameters:<br />
* <tt>cam</tt>: the device # of the camera.<br />
* <tt>duration</tt>: the duration, in seconds, of the video. The default value is 60 seconds.<br />
* <tt>format</tt>: set it to '''1''' for snapshots. If this is missing or has any other value, the archive will be a MJPEG video.<br />
<br />
<br />
Examples:<br />
<br />
* Archive a snapshot:<br />
http://ip_address:3480/data_request?id=archive_video&cam=8&format=1<br />
* Archive a 40 seconds video:<br />
http://ip_address:3480/data_request?id=archive_video&cam=3&duration=40<br />
<br />
==jobstatus==<br />
<br />
Returns the status of a job. The parameters are <tt>job</tt>, which is the job ID and optionally <tt>plugin</tt>, which is the plugin name. For a Z-Wave job the <tt>plugin</tt> parameter '''must''' be ''zwave''.<br />
<br />
If <tt>job</tt> is invalid the status returned is ''-1''.<br />
<br />
This is the list with all job statuses and their meaning:<br />
<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
Examples:<br />
<br />
http://ip_address:3480/data_request?id=jobstatus&job=13<br />
<br />
http://ip_address:3480/data_request?id=jobstatus&job=6&plugin=zwave<br />
<br />
==invoke==<br />
<br />
This request shows the list of devices and the actions they support through the UPnP services specified in their UPnP device description file. Only the actions with a star (*) preceding their name are implemented.<br />
<br />
Examples:<br />
* http://ip_address:3480/data_request?id=invoke<br />
* http://ip_address:3480/data_request?id=invoke&DeviceNum=6<br />
* http://ip_address:3480/data_request?id=invoke&UDN=uuid:4d494342-5342-5645-0002-000000000002<br />
<br />
==relay==<br />
''* UI5+ only''<br />
<br />
This tells the system to setup a relay so that you can access a device on the home network from outside the home. This is most commonly used to fetch streaming video from a camera, although it can actually be used for any IP device. This takes either an 'ip' or 'device' argument. If you pass a device, this is assumed to be a device ID, and the system will find the ip associated with that device. Optionally pass a port argument for the port you want to be relayed. Port 80 is assumed if none is specified.<br />
<br />
This request will return a 'server:port' which you can use externally to access the designated device. The relay stays open for 15 minutes before closing, at which point you will need to open it again.<br />
<br />
So, assume you have a NAS device on the home network with the ip 192.168.1.55 and the NAS runs a configuration web page on port 80. You could access it by doing this:<br />
<br />
http://ip_address:3480/data_request?id=relay&ip=192.168.1.55&port=80<br />
<br />
and if the response is: "someserver:20202", then for 15 minutes, "http://someserver:20202" will be the NAS's configuration page.<br />
<br />
If you want to access a camera, device #5, then do this:<br />
<br />
http://ip_address:3480/data_request?id=relay&device=5<br />
<br />
and if the response is "someserver:20211" and if the 'DirectStreamURL' variable (or 'streaming' in sdata) is "video.mpeg", then for 15 minutes you can access the camera's video at: http://someserver:20211/video.mpeg<br />
<br />
Note that if the device, the camera in this case, has http authentication, you will need to pass this too. So, if the user_data or sdata for the camera shows the user name is 'johndoe' and the password is 'john123', then you can view the video at: http://john:john123@someserver:20211/video.mpeg<br />
<br />
IMPORTANT: For the 15 minutes while this relay is open, it will be open to anyone on the internet and public. Therefore, for security, you may want to add to the data_request the argument externalip=x where x is the routable, external IP, such as 70.182.172.111, of the device that will be accessing the port. This means that when the relay is open, a firewall will be set on the relay server so that incoming connections are only accepted from that ip address.<br />
<br />
==update_plugin==<br />
''* UI5+ only''<br />
<br />
Updates a plugin to the latest version.<br />
<br />
Example: http://ip_address:3480/data_request?id=update_plugin&Plugin=Plugin_ID<br />
<br />
<br />
== Accessing Vera remotely through the MiOS servers ==<br />
<br />
The MiOS servers provide a secure way to remotely access and control your Vera system using the HTTP interface. Everything you can do locally with Vera on port 3480, you can do remotely with MiOS using the exact same syntax. You only need to pass the MiOS account username and password on the URL to the remote access server, along with the serial number of the unit (ie. 30012345). For example, the <tt>user_data</tt> request syntax over the Internet through the MiOS servers is:<br />
<br />
https://fwdX.mios.com/demovera/myvera123/30012345/data_request?id=user_data<br />
<br />
assuming ''demovera'' is the MiOS account username and ''myvera123'' is the password. '''Note that since the request is <tt>https</tt>, the username and password are encrypted because <tt>https</tt> encrypts the URL's as well as the contents of the page.'''<br />
<br />
==What to do when the HTTP request string is too long==<br />
<br />
http://forum.micasaverde.com/index.php/topic,10170.msg69530.html#msg69530</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DevicesLuup Devices2014-03-15T22:33:40Z<p>A-lurker: /* SetLoadLevelTarget */</p>
<hr />
<div>[[Category:Development]]<br />
== Home Automation Device ==<br />
The generic device type that all MiOS devices implement.<br />
===Service===<br />
*urn:micasaverde-com:serviceId:HaDevice1<br />
===Variables===<br />
===Actions===<br />
== Switch ==<br />
A binary switch, for an appliance, florescent light, or a relay.<br />
===Service===<br />
*urn:upnp-org:serviceId:SwitchPower1<br />
===Variables===<br />
====Status====<br />
The state of the device. 1 is on, 0 is off.<br />
===Actions===<br />
====SetTarget====<br />
Sets the target state of the switch<br />
<br />
'''Arguments:''' NewTargetValue(Integer)<br />
<br />
NewTargetValue: 1 for on, 0 for off.<br />
== Dimmer ==<br />
A dimmable light.<br />
===Service===<br />
*urn:upnp-org:serviceId:Dimming1<br />
===Variables===<br />
newTargetValue: an integer between 0 and 100 indicating the desired % of lighting.<br />
===Actions===<br />
====SetLoadLevelTarget====<br />
Sets the desired brightness or load level for the dimming device.<br />
<br />
<br />
'''Arguments:''' newLoadlevelTarget(Integer)<br />
<br />
newLoadlevelTarget: an integer between 0 and 100 indicating the desired % of lighting/load.<br />
<br />
== Sensor ==<br />
A security sensor, or any device that can be "tripped"<br />
===Service===<br />
*urn:micasaverde-com:serviceId:SecuritySensor<br />
===Variables===<br />
====Armed====<br />
An integer. 1 for armed, 0 for disarmed. This is an internal state variable of the MiOS engine and has nothing to do with the state of the sensor.<br />
====Tripped====<br />
An integer. 1 for tripped, 0 for not.<br />
====LastTrip====<br />
An integer with the UNIX timestamp of the last trip event that the MiOS engine recorded.<br />
===Actions===<br />
====SetArmed====<br />
Sets the armed state of the sensor.<br />
<br />
'''Arguments:''' newArmedValue(Integer)<br />
<br />
newArmedValue: 1 for armed, 0 for disarmed.<br />
== HVAC/Climate ==<br />
<br />
=== Service ===<br />
<br />
*urn:upnp-org:serviceId:HVAC_UserOperatingMode1 <br />
**For action SetModeTarget <br />
*urn:upnp-org:serviceId:TemperatureSetpoint1_Heat <br />
**For action NewCurrentSetpoint <br />
*urn:upnp-org:serviceId:TemperatureSetpoint1_Cool <br />
**For action NewCurrentSetpoint <br />
*urn:upnp-org:serviceId:HVAC_FanOperatingMode1 <br />
**For action SetMode<br />
<br />
=== Variables ===<br />
<br />
==== urn:upnp-org:serviceId:HVAC_UserOperatingMode1 -- ModeStatus ====<br />
<br />
One of Off, HeatOn, CoolOn, or AutoChangeOver depending on the User operation mode of the thermostat. <br />
<br />
==== urn:upnp-org:serviceId:HVAC_FanOperatingMode1 -- Mode ====<br />
<br />
One of Auto, ContinuousOn, or PeriodicOn depending on the fan mode of the thermostat.<br />
<br />
==== urn:upnp-org:serviceId:TemperatureSensor1 -- CurrentTemperature ====<br />
<br />
An integer value representing the temperature that the thermostat is reporting. <br />
<br />
==== urn:upnp-org:serviceId:TemperatureSetpoint1_Heat -- CurrentSetpoint ====<br />
<br />
An integer value representing the heat setpoint for the thermostat <br />
<br />
==== urn:upnp-org:serviceId:TemperatureSetpoint1_Cool -- CurrentSetpoint ====<br />
<br />
An integer value representing the cool setpoint for the thermostat <br />
<br />
=== Actions ===<br />
<br />
==== SetModeTarget ====<br />
<br />
Sets the desired operation mode for the thermostat. <br />
<br />
'''Arguments:''' NewModeTarget(String) <br />
<br />
NewModeTarget: One of Off, CoolOn, HeatOn, or AutoChangeOver. <br />
<br />
==== SetCurrentSetpoint ====<br />
<br />
Sets the desired setpoint. User serviceId urn:upnp-org:serviceId:TemperatureSetpoint1_Heat for heat and urn:upnp-org:serviceId:TemperatureSetpoint1_Cool for cooling <br />
<br />
'''Arguments:''' NewCurrentSetpoint(Integer) <br />
<br />
NewCurrentSetpoint: An integer value between 0 and 100 specifying the desired heat or cooling setpoint. <br />
<br />
==== SetMode ====<br />
<br />
Sets the fan operation mode for the thermostat <br />
<br />
'''Arguments:''' NewMode(String) <br />
<br />
NewMode: One of ContinuousOn or Auto, depending on the desired mode of operation for the HVAC system's fan. <br />
<br />
=== Examples ===<br />
<br />
Set Thermostat #4 to Cool: veraIP:3480/data_request?id=lu_action&amp;DeviceNum=4&amp;serviceId=urn:upnp-org:serviceId:HVAC_UserOperatingMode1&amp;action=SetModeTarget&amp;NewModeTarget=CoolOn <br />
<br />
Set Thermostat #98 cool setpoint to 70: veraIP:3480/data_request?id=lu_action&amp;DeviceNum=98&amp;serviceId=urn:upnp-org:serviceId:TemperatureSetpoint1_Cool&amp;action=SetCurrentSetpoint&amp;NewCurrentSetpoint=70<br />
<br />
== Lock ==<br />
Lorem ipsum dolor sit amet, ultricies ligula ligula maecenas, sed laboriosam erat.<br />
===Service===<br />
urn:micasaverde-com:serviceId:DoorLock1<br />
===Variables===<br />
====Status====<br />
1 for locked, 0 for unlocked<br />
===Actions===<br />
====SetTarget====<br />
Sets the desired lock state of the door lock<br />
<br />
'''Arguments:''' newTargetValue(integer)<br />
<br />
newTargetValue: 1 for lock, 0 for unlock<br />
== Window Covering ==<br />
Blinds and shades.<br />
===Service===<br />
*urn:upnp-org:serviceId:WindowCovering1<br />
*urn:upnp-org:serviceId:SwitchPower1<br />
**Supports the same actions and variables as a switch. On is closed, Off is open.<br />
===Actions===<br />
====Up====<br />
Moves the covering up.<br />
====Down====<br />
Moves the covering down.<br />
====Stop====<br />
Stops the covering moving.<br />
== Sensor - Humidity ==<br />
A humidity sensor.<br />
===Service===<br />
urn:micasaverde-com:serviceId:HumiditySensor1<br />
===Variables===<br />
CurrentLevel(Integer): The current humidity level that the sensor is reporting.<br />
== Sensor - Temperature ==<br />
A temperature sensor.<br />
===Service===<br />
urn:upnp-org:serviceId:TemperatureSensor1<br />
===Variables===<br />
CurrentLevel(Integer): The current temperature in degrees the sensor is reporting.<br />
== Sensor - Light ==<br />
A light sensor.<br />
===Service===<br />
urn:schemas-micasaverde-com:service:LightSensor:1<br />
===Variables===<br />
CurrentLevel(Integer): The current light level that the sensor is reporting.<br />
== Power Meter ==<br />
A power meter or a device that can display power usage.<br />
===Service===<br />
urn:micasaverde-com:serviceId:EnergyMetering1<br />
===Variables===<br />
Watts(Integer): The energy draw the meter is recording, in watts.</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Plugins_ByHandLuup Plugins ByHand2014-02-03T23:41:44Z<p>A-lurker: /* */</p>
<hr />
<div>[[Category:Development]]<br />
==Description of the XML files==<br />
<br />
Because the web generator is not yet operational (as of June 2013) you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins (in UI5, choose Apps, Develop Apps, Luup Files). There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click view to see the XML file. We recommend using the Firefox web browser because it has built-in support for nicely displaying XML files in a graphical tree. XML can be hard to read in other web browser.<br />
<br />
When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.<br />
<br />
The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.<br />
<br />
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.<br />
<br />
The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.<br />
<br />
== The Luup XML implementation file ==<br />
<br />
I_GC100.xml is a full featured, functional implementation for the Global Cache GC100, which is an ethernet device with relay switches, input sensors, infrared transmitters and serial ports. I_GC100.xml shows a complete Luup plugin with parent/child devices. The corresponding UPnP Device specification file is D_GC100.xml. In all the implementation files the top-level XML tag (ie root node) is called "implementation". It contains the following elements: <br />
<br />
=== <settings> ===<br />
<br />
The settings node contains various settings for the implementation. <br />
<br />
=== <protocol> ===<br />
Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. The protocol tag tells Luup what's considered a single ''chunk'' of data. By using a format, from the supported list below, you avoid byte-by-byte processing on input streams as the Luup engine will ''chunk'' the data to you and pass it to your Lua code handling <tt><incoming></tt> requests.<br />
Lua code is much cleaner when it handles data in chunks. If you have a protocol that's not natively supported, and is likely to be used by other devices, let us know and we'll add it to the Luup engine so you don't need to mess with it.<br />
<br />
Valid values for this tag are:<br />
*<tt>cr</tt> - all incoming commands are terminated with a carriage return+line character, and all outgoing data should have a cr appended. Incoming data will have the cr stripped off.<br />
*<tt>crlf</tt> - all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. Incoming data will have the cr/lf stripped off.<br />
*<tt>stxetx</tt> - all incoming commands are surrounded by STX and ETX characters. If you send the string "test" the framework will add the STX before and the ETX at the end, and if the string "<tt>''<stx>''test''<etx>''</tt>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.<br />
*<tt>raw</tt> - makes no modifications to outgoing data, and calls your incoming data callback for each byte received. This adds more overhead since the engine needs to call your Luup function for every character, and makes your code complex. So, generally avoid using '<tt>raw</tt>' and let us add support for your protocol if you have a new one we don't yet support.<br />
<br />
Caution: the <protocol> tag can be either in the I_xxxx file or the D_xxxx file or both. If the latter, they must be identical.<br />
<br />
Hint: with verbose logging enabled - log lines starting with "52" are RX data, those with "51" are TX data.<br />
<br />
=== <ioPort> ===<br />
One way to have the device talk to another device on the internet. If you put a TCP port number here, an outgoing connection attempt is made automatically when the device initializes. (The remote IP address should be placed in the 'ip' box in the device's advanced configuration tab.) If you don't use this tag, you can use luup.io.open(..) instead.<br />
<br />
=== <handleChildren> ===<br />
If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like IR transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.<br />
<br />
=== <functions> ===<br />
Put here the Lua code for functions you want to be able to use in other places in your Lua code. You can also declare local variables here before the functions are described. Watch out for XML syntax. In particular, the > and >= comparison operators should be escaped.<br />
<br />
=== <files> ===<br />
As an alternative to the <functions> element, you can put your Lua implementation in a separate file containing pure Lua code and not worry about XML escapes. By convention, the file name should begin with L_ and end with .lua. as in <files>L_MyDevice.lua</files><br />
<br />
=== <startup> ===<br />
<br />
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag. <br />
<br />
=== <actionList> ===<br />
<br />
This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file. <br />
<br />
==== function declarations ====<br />
<br />
Whatever Lua code you create in the tags will be put inside a function automatically by the Luup engine, and your code will be passed variables that are relevant to whatever the code needs to do. For example, the code inside the 'run' tag is passed lul_device,lul_settings where lul_device is the id of the device the action was sent to, and lul_settings has the arguments to the UPnP action. See: [[Luup Declarations]] for details.<br />
<br />
There are several different nodes you can put within the "action" node. <br />
<br />
==== <run/job/incoming/timeout> ====<br />
<br />
Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want. <br />
<br />
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on. <br />
<br />
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time. <br />
<br />
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values:<br />
<br />
1) the status of the job<br />
<br />
2) how long to wait before the job times out in seconds.<br />
<br />
The status can be:<br />
*0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon. It means it's waiting to start. If you return this value your 'job' code will be run again in the 'timeout' seconds <br />
*2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon. This means the job failed. Your code won't be run again. <br />
*4=job_Done: In vera's UI a job in this state is displayed as a green icon. This means the job finished ok. Your code won't be run again. <br />
*5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon. This means the job is running and you're waiting for return data. Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag. If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run. <br />
<br />
So in the SendProntoCode job code for the I_GC100, we return 5,10 which means we're waiting for data and should wait up to 10 seconds. <br />
<br />
While a job is active, a UPnP control point will see the status of the job when it uses the GetStatus action for Vera. In Vera's web ui an active job is shown as an icon next to the device. While Luup is in the middle of executing the Lua code for your job, the status is reported to the control point as 1=job_InProgress. <br />
<br />
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the I/O port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not. <br />
<br />
The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup Lua extensions]] lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <incoming> ====<br />
<br />
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic I/O port. The Luup engine handles low-level I/O for these types of ports so you only need to call one of the [[Luup Lua extensions]] to send data on the port. <br />
<br />
This is the general purpose incoming data handler that the Luup engine will call whenever data comes in on an I/O Port. It has a node 'lua' which contains the Lua code that is called. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <ir> ====<br />
<br />
Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the [[Luup IO Device]] in the [[Luup Configuration File]]. Note that other formats besides pronto format can be used. Refer to the [[Luup_Declarations#<ir>|IR tag]]<br />
<br />
==Walkthrough to create a device==<br />
<br />
We have documented step-by-step the detailed process of creating a Luup interface, including everything that was done to debug, for Somfy blind control device (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]), which is a simple 1-way serial device (ie send data, but don't get any response) here: [[Luup_Somfy_Walkthrough]]<br />
<br />
No matter type of Luup plugin you'll be creating the procedure to create it is the same. This is true for Luup plugins which do not talk to any external hardware, such as a 'Weather' plugin to provide weather services, as well as plugins that talk to infrared devices like a TV, and serial/network devices.<br />
<br />
The first step is to create the UPnP device specification and service specification files. Whenever possible you will want to re-use existing service specification files, as explained in "Introduction to UPnP" [[Luup_Plugins#Introduction_to_UPnP here]]. And if your device is functionally the same as an existing UPnP Device type, like a light switch, you should re-use an existing UPnP Device Specification file also so that a UPnP Control Point will know how to control your device with modification. A list of all currently known UPnP Device and Service specification files is here: [[Luup_UPNP_Files]]. Find the existing UPnP device file that is most similar to the Luup plugin you are creating, and list all the services that device will implement. Download these files on your computer either from the [[Luup_UPNP_Files]] page, or by going to Vera's setup page and choosing Devices, Luup plugins.<br />
<br />
Modify the UPnP device specification file as needed, such as changing the manufacturer and model. Do not worry about the UDN tag as Luup will create a UDN for you automatically. If your device is functionally the same as the template you started from, leave the deviceType the same. If it's different, modify the deviceType replacing the "schemas-upnp-org" or "micasaverde-com" with some domain that you have, or if you don't have one, just use your name. Leave the :device: but change the word after it to describe your device. The deviceType must contain only a-z, 0-9, : and -.<br />
<br />
In the '''<serviceList>''' tag, add all the services you will implement. You can also create new services at this time. Save your new device file and service files as D_[some name] and S_[some name]. This is not mandated by UPnP, but is a convention we use so it's easy to recognize the file type by the name.<br />
<br />
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.<br />
<br />
To add the device to Vera's configuration file, so Vera will load and use the device, go to the Devices tab and at the bottom fill in the UPnP Device filename in the 'Add Device' box and pick a room. When you save your changes, which causes the Luup engine to reload the new configuration, it will look for the device, service and implementation files to start the device. If the files don't already exist on Vera, Vera will log an error and will not start the device. So you need to upload any new files you created by going to the Devices, Luup plugins page in Vera's setup web page. You can upload several files at once. The files will not be used until the Loop engine is reset so you probably want to check the "Restart Luup after upload" box before you click 'go'. If you upload the files without checking the box and want to restart the Luup engine, just click 'save', even if the button is grayed out. You will likely need to make several changes to the files before they're right so you can leave one web browser open to the 'Luup plugin' page and just modify the files in your text editor then click 'go' again to re-upload them after saving your changes. You can open another browser window or tab to access other pages in Vera's web ui and control the device while leaving the list of files to upload on the Luup plugin page intact so you can re-upload by clicking 'go'.<br />
<br />
Next you'll want to know how to debug your Luup plugins and Lua code. See [[Luup_Debugging]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-03T23:40:26Z<p>A-lurker: /* function: is_ready */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Caution 1 - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
Caution 2 - this code is also incorrect:<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tostring only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the luup.chdev.start call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
device_type is the UPnP device type, such as urn:schemas-upnp-org:device:BinaryLight:1.<br />
<br />
If device_filename is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any device_type you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in implementation_filename. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in implementation_filename.<br />
<br />
If embedded is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The parameters are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-03T23:38:50Z<p>A-lurker: /* function: write */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
The device parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Caution 1 - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
Caution 2 - this code is also incorrect:<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tostring only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the luup.chdev.start call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
device_type is the UPnP device type, such as urn:schemas-upnp-org:device:BinaryLight:1.<br />
<br />
If device_filename is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any device_type you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in implementation_filename. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in implementation_filename.<br />
<br />
If embedded is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The parameters are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred.<br />
<br />
The written data is modified depending upon the value of the [[Luup_Plugins_ByHand#<protocol>|<protocol> tag]].<br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Plugins_ByHandLuup Plugins ByHand2014-02-03T23:28:24Z<p>A-lurker: /* */</p>
<hr />
<div>[[Category:Development]]<br />
==Description of the XML files==<br />
<br />
Because the web generator is not yet operational (as of June 2013) you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins (in UI5, choose Apps, Develop Apps, Luup Files). There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click view to see the XML file. We recommend using the Firefox web browser because it has built-in support for nicely displaying XML files in a graphical tree. XML can be hard to read in other web browser.<br />
<br />
When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.<br />
<br />
The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.<br />
<br />
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.<br />
<br />
The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.<br />
<br />
== The Luup XML implementation file ==<br />
<br />
I_GC100.xml is a full featured, functional implementation for the Global Cache GC100, which is an ethernet device with relay switches, input sensors, infrared transmitters and serial ports. I_GC100.xml shows a complete Luup plugin with parent/child devices. The corresponding UPnP Device specification file is D_GC100.xml. In all the implementation files the top-level XML tag (ie root node) is called "implementation". It contains the following elements: <br />
<br />
=== <settings> ===<br />
<br />
The settings node contains various settings for the implementation. <br />
<br />
=== <protocol> ===<br />
Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. The protocol tag tells Luup what's considered a single ''chunk'' of data. By using a format, from the supported list below, you avoid byte-by-byte processing on input streams as the Luup engine will ''chunk'' the data to you and pass it to your Lua code handling <tt><incoming></tt> requests.<br />
Lua code is much cleaner when it handles data in chunks. If you have a protocol that's not natively supported, and is likely to be used by other devices, let us know and we'll add it to the Luup engine so you don't need to mess with it.<br />
<br />
Valid values for this tag are:<br />
*<tt>cr</tt> - all incoming commands are terminated with a carriage return+line character, and all outgoing data should have a cr appended. Incoming data will have the cr stripped off.<br />
*<tt>crlf</tt> - all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. Incoming data will have the cr/lf stripped off.<br />
*<tt>stxetx</tt> - all incoming commands are surrounded by STX and ETX characters. If you send the string "test" the framework will add the STX before and the ETX at the end, and if the string "<tt>''<stx>''test''<etx>''</tt>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.<br />
*<tt>raw</tt> - makes no modifications to outgoing data, and calls your incoming data callback for each byte received. This adds more overhead since the engine needs to call your Luup function for every character, and makes your code complex. So, generally avoid using '<tt>raw</tt>' and let us add support for your protocol if you have a new one we don't yet support.<br />
<br />
Caution: The <protocol> tag can be either in the I_xxxx file or the D_xxxx file or both. If the latter, they must be identical.<br />
<br />
Hint: With verbose logging enabled - log lines starting with "52" are RX data, those with "51" are TX data.<br />
<br />
=== <ioPort> ===<br />
One way to have the device talk to another device on the internet. If you put a TCP port number here, an outgoing connection attempt is made automatically when the device initializes. (The remote IP address should be placed in the 'ip' box in the device's advanced configuration tab.) If you don't use this tag, you can use luup.io.open(..) instead.<br />
<br />
=== <handleChildren> ===<br />
If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like IR transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.<br />
<br />
=== <functions> ===<br />
Put here the Lua code for functions you want to be able to use in other places in your Lua code. You can also declare local variables here before the functions are described. Watch out for XML syntax. In particular, the > and >= comparison operators should be escaped.<br />
<br />
=== <files> ===<br />
As an alternative to the <functions> element, you can put your Lua implementation in a separate file containing pure Lua code and not worry about XML escapes. By convention, the file name should begin with L_ and end with .lua. as in <files>L_MyDevice.lua</files><br />
<br />
=== <startup> ===<br />
<br />
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag. <br />
<br />
=== <actionList> ===<br />
<br />
This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file. <br />
<br />
==== function declarations ====<br />
<br />
Whatever Lua code you create in the tags will be put inside a function automatically by the Luup engine, and your code will be passed variables that are relevant to whatever the code needs to do. For example, the code inside the 'run' tag is passed lul_device,lul_settings where lul_device is the id of the device the action was sent to, and lul_settings has the arguments to the UPnP action. See: [[Luup Declarations]] for details.<br />
<br />
There are several different nodes you can put within the "action" node. <br />
<br />
==== <run/job/incoming/timeout> ====<br />
<br />
Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want. <br />
<br />
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on. <br />
<br />
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time. <br />
<br />
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values:<br />
<br />
1) the status of the job<br />
<br />
2) how long to wait before the job times out in seconds.<br />
<br />
The status can be:<br />
*0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon. It means it's waiting to start. If you return this value your 'job' code will be run again in the 'timeout' seconds <br />
*2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon. This means the job failed. Your code won't be run again. <br />
*4=job_Done: In vera's UI a job in this state is displayed as a green icon. This means the job finished ok. Your code won't be run again. <br />
*5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon. This means the job is running and you're waiting for return data. Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag. If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run. <br />
<br />
So in the SendProntoCode job code for the I_GC100, we return 5,10 which means we're waiting for data and should wait up to 10 seconds. <br />
<br />
While a job is active, a UPnP control point will see the status of the job when it uses the GetStatus action for Vera. In Vera's web ui an active job is shown as an icon next to the device. While Luup is in the middle of executing the Lua code for your job, the status is reported to the control point as 1=job_InProgress. <br />
<br />
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the I/O port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not. <br />
<br />
The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup Lua extensions]] lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <incoming> ====<br />
<br />
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic I/O port. The Luup engine handles low-level I/O for these types of ports so you only need to call one of the [[Luup Lua extensions]] to send data on the port. <br />
<br />
This is the general purpose incoming data handler that the Luup engine will call whenever data comes in on an I/O Port. It has a node 'lua' which contains the Lua code that is called. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <ir> ====<br />
<br />
Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the [[Luup IO Device]] in the [[Luup Configuration File]]. Note that other formats besides pronto format can be used. Refer to the [[Luup_Declarations#<ir>|IR tag]]<br />
<br />
==Walkthrough to create a device==<br />
<br />
We have documented step-by-step the detailed process of creating a Luup interface, including everything that was done to debug, for Somfy blind control device (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]), which is a simple 1-way serial device (ie send data, but don't get any response) here: [[Luup_Somfy_Walkthrough]]<br />
<br />
No matter type of Luup plugin you'll be creating the procedure to create it is the same. This is true for Luup plugins which do not talk to any external hardware, such as a 'Weather' plugin to provide weather services, as well as plugins that talk to infrared devices like a TV, and serial/network devices.<br />
<br />
The first step is to create the UPnP device specification and service specification files. Whenever possible you will want to re-use existing service specification files, as explained in "Introduction to UPnP" [[Luup_Plugins#Introduction_to_UPnP here]]. And if your device is functionally the same as an existing UPnP Device type, like a light switch, you should re-use an existing UPnP Device Specification file also so that a UPnP Control Point will know how to control your device with modification. A list of all currently known UPnP Device and Service specification files is here: [[Luup_UPNP_Files]]. Find the existing UPnP device file that is most similar to the Luup plugin you are creating, and list all the services that device will implement. Download these files on your computer either from the [[Luup_UPNP_Files]] page, or by going to Vera's setup page and choosing Devices, Luup plugins.<br />
<br />
Modify the UPnP device specification file as needed, such as changing the manufacturer and model. Do not worry about the UDN tag as Luup will create a UDN for you automatically. If your device is functionally the same as the template you started from, leave the deviceType the same. If it's different, modify the deviceType replacing the "schemas-upnp-org" or "micasaverde-com" with some domain that you have, or if you don't have one, just use your name. Leave the :device: but change the word after it to describe your device. The deviceType must contain only a-z, 0-9, : and -.<br />
<br />
In the '''<serviceList>''' tag, add all the services you will implement. You can also create new services at this time. Save your new device file and service files as D_[some name] and S_[some name]. This is not mandated by UPnP, but is a convention we use so it's easy to recognize the file type by the name.<br />
<br />
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.<br />
<br />
To add the device to Vera's configuration file, so Vera will load and use the device, go to the Devices tab and at the bottom fill in the UPnP Device filename in the 'Add Device' box and pick a room. When you save your changes, which causes the Luup engine to reload the new configuration, it will look for the device, service and implementation files to start the device. If the files don't already exist on Vera, Vera will log an error and will not start the device. So you need to upload any new files you created by going to the Devices, Luup plugins page in Vera's setup web page. You can upload several files at once. The files will not be used until the Loop engine is reset so you probably want to check the "Restart Luup after upload" box before you click 'go'. If you upload the files without checking the box and want to restart the Luup engine, just click 'save', even if the button is grayed out. You will likely need to make several changes to the files before they're right so you can leave one web browser open to the 'Luup plugin' page and just modify the files in your text editor then click 'go' again to re-upload them after saving your changes. You can open another browser window or tab to access other pages in Vera's web ui and control the device while leaving the list of files to upload on the Luup plugin page intact so you can re-upload by clicking 'go'.<br />
<br />
Next you'll want to know how to debug your Luup plugins and Lua code. See [[Luup_Debugging]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Plugins_ByHandLuup Plugins ByHand2014-02-03T23:25:11Z<p>A-lurker: /* */</p>
<hr />
<div>[[Category:Development]]<br />
==Description of the XML files==<br />
<br />
Because the web generator is not yet operational (as of June 2013) you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins (in UI5, choose Apps, Develop Apps, Luup Files). There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click view to see the XML file. We recommend using the Firefox web browser because it has built-in support for nicely displaying XML files in a graphical tree. XML can be hard to read in other web browser.<br />
<br />
When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.<br />
<br />
The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.<br />
<br />
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.<br />
<br />
The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.<br />
<br />
== The Luup XML implementation file ==<br />
<br />
I_GC100.xml is a full featured, functional implementation for the Global Cache GC100, which is an ethernet device with relay switches, input sensors, infrared transmitters and serial ports. I_GC100.xml shows a complete Luup plugin with parent/child devices. The corresponding UPnP Device specification file is D_GC100.xml. In all the implementation files the top-level XML tag (ie root node) is called "implementation". It contains the following elements: <br />
<br />
=== <settings> ===<br />
<br />
The settings node contains various settings for the implementation. <br />
<br />
=== <protocol> ===<br />
Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. The protocol tag tells Luup what's considered a single ''chunk'' of data. By using a format, from the supported list below, you avoid byte-by-byte processing on input streams as the Luup engine will ''chunk'' the data to you and pass it to your Lua code handling <tt><incoming></tt> requests.<br />
Lua code is much cleaner when it handles data in chunks. If you have a protocol that's not natively supported, and is likely to be used by other devices, let us know and we'll add it to the Luup engine so you don't need to mess with it.<br />
<br />
Valid values for this tag are:<br />
*<tt>cr</tt> - all incoming commands are terminated with a carriage return+line character, and all outgoing data should have a cr appended. Incoming data will have the cr stripped off.<br />
*<tt>crlf</tt> - all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. Incoming data will have the cr/lf stripped off.<br />
*<tt>stxetx</tt> - all incoming commands are surrounded by STX and ETX characters. If you send the string "test" the framework will add the STX before and the ETX at the end, and if the string "<tt>''<stx>''test''<etx>''</tt>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.<br />
*<tt>raw</tt> - makes no modifications to outgoing data, and calls your incoming data callback for each byte received. This adds more overhead since the engine needs to call your Luup function for every character, and makes your code complex. So, generally avoid using '<tt>raw</tt>' and let us add support for your protocol if you have a new one we don't yet support.<br />
<br />
Caution: The <protocol> tag can be either in the I_xxxx file or the D_xxxx file or both. If the latter, they must be identical.<br />
<br />
=== <ioPort> ===<br />
One way to have the device talk to another device on the internet. If you put a TCP port number here, an outgoing connection attempt is made automatically when the device initializes. (The remote IP address should be placed in the 'ip' box in the device's advanced configuration tab.) If you don't use this tag, you can use luup.io.open(..) instead.<br />
<br />
=== <handleChildren> ===<br />
If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like IR transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.<br />
<br />
=== <functions> ===<br />
Put here the Lua code for functions you want to be able to use in other places in your Lua code. You can also declare local variables here before the functions are described. Watch out for XML syntax. In particular, the > and >= comparison operators should be escaped.<br />
<br />
=== <files> ===<br />
As an alternative to the <functions> element, you can put your Lua implementation in a separate file containing pure Lua code and not worry about XML escapes. By convention, the file name should begin with L_ and end with .lua. as in <files>L_MyDevice.lua</files><br />
<br />
=== <startup> ===<br />
<br />
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag. <br />
<br />
=== <actionList> ===<br />
<br />
This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file. <br />
<br />
==== function declarations ====<br />
<br />
Whatever Lua code you create in the tags will be put inside a function automatically by the Luup engine, and your code will be passed variables that are relevant to whatever the code needs to do. For example, the code inside the 'run' tag is passed lul_device,lul_settings where lul_device is the id of the device the action was sent to, and lul_settings has the arguments to the UPnP action. See: [[Luup Declarations]] for details.<br />
<br />
There are several different nodes you can put within the "action" node. <br />
<br />
==== <run/job/incoming/timeout> ====<br />
<br />
Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want. <br />
<br />
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on. <br />
<br />
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time. <br />
<br />
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values:<br />
<br />
1) the status of the job<br />
<br />
2) how long to wait before the job times out in seconds.<br />
<br />
The status can be:<br />
*0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon. It means it's waiting to start. If you return this value your 'job' code will be run again in the 'timeout' seconds <br />
*2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon. This means the job failed. Your code won't be run again. <br />
*4=job_Done: In vera's UI a job in this state is displayed as a green icon. This means the job finished ok. Your code won't be run again. <br />
*5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon. This means the job is running and you're waiting for return data. Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag. If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run. <br />
<br />
So in the SendProntoCode job code for the I_GC100, we return 5,10 which means we're waiting for data and should wait up to 10 seconds. <br />
<br />
While a job is active, a UPnP control point will see the status of the job when it uses the GetStatus action for Vera. In Vera's web ui an active job is shown as an icon next to the device. While Luup is in the middle of executing the Lua code for your job, the status is reported to the control point as 1=job_InProgress. <br />
<br />
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the I/O port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not. <br />
<br />
The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup Lua extensions]] lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <incoming> ====<br />
<br />
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic I/O port. The Luup engine handles low-level I/O for these types of ports so you only need to call one of the [[Luup Lua extensions]] to send data on the port. <br />
<br />
This is the general purpose incoming data handler that the Luup engine will call whenever data comes in on an I/O Port. It has a node 'lua' which contains the Lua code that is called. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <ir> ====<br />
<br />
Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the [[Luup IO Device]] in the [[Luup Configuration File]]. Note that other formats besides pronto format can be used. Refer to the [[Luup_Declarations#<ir>|IR tag]]<br />
<br />
==Walkthrough to create a device==<br />
<br />
We have documented step-by-step the detailed process of creating a Luup interface, including everything that was done to debug, for Somfy blind control device (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]), which is a simple 1-way serial device (ie send data, but don't get any response) here: [[Luup_Somfy_Walkthrough]]<br />
<br />
No matter type of Luup plugin you'll be creating the procedure to create it is the same. This is true for Luup plugins which do not talk to any external hardware, such as a 'Weather' plugin to provide weather services, as well as plugins that talk to infrared devices like a TV, and serial/network devices.<br />
<br />
The first step is to create the UPnP device specification and service specification files. Whenever possible you will want to re-use existing service specification files, as explained in "Introduction to UPnP" [[Luup_Plugins#Introduction_to_UPnP here]]. And if your device is functionally the same as an existing UPnP Device type, like a light switch, you should re-use an existing UPnP Device Specification file also so that a UPnP Control Point will know how to control your device with modification. A list of all currently known UPnP Device and Service specification files is here: [[Luup_UPNP_Files]]. Find the existing UPnP device file that is most similar to the Luup plugin you are creating, and list all the services that device will implement. Download these files on your computer either from the [[Luup_UPNP_Files]] page, or by going to Vera's setup page and choosing Devices, Luup plugins.<br />
<br />
Modify the UPnP device specification file as needed, such as changing the manufacturer and model. Do not worry about the UDN tag as Luup will create a UDN for you automatically. If your device is functionally the same as the template you started from, leave the deviceType the same. If it's different, modify the deviceType replacing the "schemas-upnp-org" or "micasaverde-com" with some domain that you have, or if you don't have one, just use your name. Leave the :device: but change the word after it to describe your device. The deviceType must contain only a-z, 0-9, : and -.<br />
<br />
In the '''<serviceList>''' tag, add all the services you will implement. You can also create new services at this time. Save your new device file and service files as D_[some name] and S_[some name]. This is not mandated by UPnP, but is a convention we use so it's easy to recognize the file type by the name.<br />
<br />
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.<br />
<br />
To add the device to Vera's configuration file, so Vera will load and use the device, go to the Devices tab and at the bottom fill in the UPnP Device filename in the 'Add Device' box and pick a room. When you save your changes, which causes the Luup engine to reload the new configuration, it will look for the device, service and implementation files to start the device. If the files don't already exist on Vera, Vera will log an error and will not start the device. So you need to upload any new files you created by going to the Devices, Luup plugins page in Vera's setup web page. You can upload several files at once. The files will not be used until the Loop engine is reset so you probably want to check the "Restart Luup after upload" box before you click 'go'. If you upload the files without checking the box and want to restart the Luup engine, just click 'save', even if the button is grayed out. You will likely need to make several changes to the files before they're right so you can leave one web browser open to the 'Luup plugin' page and just modify the files in your text editor then click 'go' again to re-upload them after saving your changes. You can open another browser window or tab to access other pages in Vera's web ui and control the device while leaving the list of files to upload on the Luup plugin page intact so you can re-upload by clicking 'go'.<br />
<br />
Next you'll want to know how to debug your Luup plugins and Lua code. See [[Luup_Debugging]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DeclarationsLuup Declarations2014-02-03T23:15:28Z<p>A-lurker: </p>
<hr />
<div>[[Category:Development]]<br />
When the Luup engine calls your Lua code it will pass your code some variables relevant to whatever the code is supposed to do. You can see how your code looks with the full function declarations by bringing up this page in a web browser: http://ip_of_vera:49451/data_request?id=lu_lua&DeviceNum=8<br />
<br />
Here is a list of the variables that are passed to your Lua code, and also a list of what return values the Lua code should send back. The tag names are explained here: [[Luup_Plugins_ByHand#The_Luup_XML_implementation_file|The Luup XML implementation file]].<br />
<br />
==<run>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. <br />
<br />
return value: true or false where true means the function ran ok, false means it failed. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SetTarget_run(lul_device, lul_settings)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue))<br />
return false -- function failed<br />
<br />
end</source><br />
<br />
==<job>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. lul_job is the id number of the job. <br />
<br />
return value: return 2 values with the syntax return a,b. The first is the job status and is a number from 0-5, and the second is the timeout in seconds.<br />
<br />
For more detail see:<br />
*[[Luup_Lua_extensions#Module:_luup.job|Luup Lua extensions: Module luup.job]]<br />
*[[Luup_Plugins_ByHand#.3Crun.2Fjob.2Fincoming.2Ftimeout.3E|Luup Plugins ByHand: <run/job/incoming/timeout>]]<br />
*[[UI_Notes#Job_status|UI Notes: Job status]]<br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_job(lul_device, lul_settings, lul_job)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job)<br />
-- 5 = job_WaitingForCallback<br />
-- and we'll wait 10 seconds for incoming data<br />
return 5, 10<br />
<br />
end</source><br />
<br />
==<incoming> (returned by a job) ==<br />
<br />
variables: same as for job above, plus lul_data which is a binary string with the data received <br />
<br />
return value: return 3 values with the syntax return a,b,c. The first two are the same as with job, and the 3rd is a true or false indicating if the incoming data was intended for this job. See [[Luup_Plugins_ByHand#<run/job/incoming/timeout>|<run/job/incoming/timeout>]] for details. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_incoming(lul_device, lul_settings, lul_job, lul_data)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job .. " received data: " .. lul_data)<br />
-- 4 = jobDone<br />
-- nil = n/a on the timeout since the job is done<br />
-- true = the incoming data was for us<br />
return 4, nil, true<br />
<br />
end</source><br />
<br />
==<timeout>==<br />
<br />
variables: same as for job above.<br />
<br />
return value: same as for job above<br />
<br />
==<incoming> (general, not for a job)==<br />
<br />
variables: lul_device is a number that is the device id. lul_data is a binary string with the data received<br />
<br />
return values: none<br />
<br />
==<scene>==<br />
<br />
variables: none<br />
<br />
return values: boolean (true or false)<br />
<br />
This is how the Lua code in a scene is called. The code is ran before the commands in the scene and if the code returns false, the scene is aborted and the command don't run. If anything else is returned, including true or nothing at all, the scene's commands run as normal.<br />
<br />
==<event>==<br />
<br />
variables: lul_value <br />
<br />
return values: boolean (true or false) <br />
<br />
This is how the Lua code in an event is called. The code is run before the event is processed and the commands in the scene are executed. If the code returns false, the event is aborted and the command(s) in the scene&nbsp;will not&nbsp;run. If anything else is returned, including true or nothing at all, the scene's commands run as normal. lul_value is the new value that is being assigned to the variable which the event is monitoring.<br />
<br />
==<request>==<br />
<br />
variables: lul_request,lul_parameters,lul_outputformat<br />
<br />
return values: lul_data,lul_outputformat<br />
<br />
When you call lu_RegisterHandler (see [[Luup_Lua_extensions]]) to handle a request on a URL, you pass in a function name that will handle the request. This function is called with lul_request as a string containing the request ID, lul_parameters is a table with all the arguments on the URL, and lul_outputformat is the requested format of the data passed on the &output_format= in the URL.<br />
<br />
You return lul_data which is a string containing the response which is forwarded to the client, and optionally lul_outputformat, which is the document type put in the HTML response.<br />
<br />
==<timed> (callback)==<br />
<br />
variables: lul_data<br />
<br />
return values: none<br />
<br />
When you pass a Lua function to be called at a later time with lu_CallFunctionDelay or lu_CallFunctionTimer (see [[Luup_Lua_extensions]]), the function is called with the data (a string) you past.<br />
<br />
==<watch> (callback)==<br />
<br />
variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new<br />
<br />
return values: none<br />
<br />
When you watch a variable with lu_WatchVariable (see [[Luup_Lua_extensions]]), and the variable changes, your callback is called with the information on the variable that changed.<br />
<br />
==<startup>==<br />
<br />
variables: lul_device<br />
<br />
return values: return 3 variables with the syntax return a,b,c where the first is true if the startup was successful or false if not, followed by 2 strings for the comments and the name of the module<br />
<br />
If this function is called in the startup sequence specified in the 'startup' XML tag, return true if the startup was ok, false if it wasn't, followed by some comments and the name of the module, like this: return false,'Cannot get state','gc100' or return true,'ok','gc100'<br />
<br />
==<ir>==<br />
There are two parts to the equation here:<br />
# the device holding the IR codes for all the different IR commands to be used<br />
# a device associated with 1) above that understands the codes and knows how to emit them via a specific piece of hardware<br />
<br />
In the user interface for device one above, you can select the hardware device, two above, that will emit the IR codes.<br />
<br />
Each <ir> tag hold an IR code - which can be in any format. In device one, you associate the hardware driver (device two). Device number two must understand the format, as found in the <ir> tag and will automagically receive the IR code via the function SendProntoCode(). The hardware is then manipulated to emit the IR code.<br />
<br />
Note that many of the IR devices used by Vera use ProntoCodes, hence the handling function was named SendProntoCode. However this is a misnomer, as the function just receives what is located in the <ir> tag. The hardware driver computer code just needs to emit the IR code it receives, whether that be a prontcode or otherwise.<br />
<br />
Refer to [[Luup_IR]] also.</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DeclarationsLuup Declarations2014-02-03T23:12:06Z<p>A-lurker: /* (returned by a job) */</p>
<hr />
<div>[[Category:Development]]<br />
When the Luup engine calls your Lua code it will pass your code some variables relevant to whatever the code is supposed to do. You can see how your code looks with the full function declarations by bringing up this page in a web browser: http://ip_of_vera:49451/data_request?id=lu_lua&DeviceNum=8<br />
<br />
Here is a list of the variables that are passed to your Lua code, and also a list of what return values the Lua code should send back. The tag names are explained here: [[Luup_Plugins_ByHand#The_Luup_XML_implementation_file]]:<br />
<br />
==<run>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. <br />
<br />
return value: true or false where true means the function ran ok, false means it failed. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SetTarget_run(lul_device, lul_settings)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue))<br />
return false -- function failed<br />
<br />
end</source><br />
<br />
==<job>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. lul_job is the id number of the job. <br />
<br />
return value: return 2 values with the syntax return a,b. The first is the job status and is a number from 0-5, and the second is the timeout in seconds.<br />
<br />
For more detail see:<br />
*[[Luup_Lua_extensions#Module:_luup.job|Luup Lua extensions: Module luup.job]]<br />
*[[Luup_Plugins_ByHand#.3Crun.2Fjob.2Fincoming.2Ftimeout.3E|Luup Plugins ByHand: <run/job/incoming/timeout>]]<br />
*[[UI_Notes#Job_status|UI Notes: Job status]]<br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_job(lul_device, lul_settings, lul_job)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job)<br />
-- 5 = job_WaitingForCallback<br />
-- and we'll wait 10 seconds for incoming data<br />
return 5, 10<br />
<br />
end</source><br />
<br />
==<incoming> (returned by a job) ==<br />
<br />
variables: same as for job above, plus lul_data which is a binary string with the data received <br />
<br />
return value: return 3 values with the syntax return a,b,c. The first two are the same as with job, and the 3rd is a true or false indicating if the incoming data was intended for this job. See [[Luup_Plugins_ByHand#<run/job/incoming/timeout>|<run/job/incoming/timeout>]] for details. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_incoming(lul_device, lul_settings, lul_job, lul_data)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job .. " received data: " .. lul_data)<br />
-- 4 = jobDone<br />
-- nil = n/a on the timeout since the job is done<br />
-- true = the incoming data was for us<br />
return 4, nil, true<br />
<br />
end</source><br />
<br />
==<timeout>==<br />
<br />
variables: same as for job above.<br />
<br />
return value: same as for job above<br />
<br />
==<incoming> (general, not for a job)==<br />
<br />
variables: lul_device is a number that is the device id. lul_data is a binary string with the data received<br />
<br />
return values: none<br />
<br />
==<scene>==<br />
<br />
variables: none<br />
<br />
return values: boolean (true or false)<br />
<br />
This is how the Lua code in a scene is called. The code is ran before the commands in the scene and if the code returns false, the scene is aborted and the command don't run. If anything else is returned, including true or nothing at all, the scene's commands run as normal.<br />
<br />
==<event>==<br />
<br />
variables: lul_value <br />
<br />
return values: boolean (true or false) <br />
<br />
This is how the Lua code in an event is called. The code is run before the event is processed and the commands in the scene are executed. If the code returns false, the event is aborted and the command(s) in the scene&nbsp;will not&nbsp;run. If anything else is returned, including true or nothing at all, the scene's commands run as normal. lul_value is the new value that is being assigned to the variable which the event is monitoring.<br />
<br />
==<request>==<br />
<br />
variables: lul_request,lul_parameters,lul_outputformat<br />
<br />
return values: lul_data,lul_outputformat<br />
<br />
When you call lu_RegisterHandler (see [[Luup_Lua_extensions]]) to handle a request on a URL, you pass in a function name that will handle the request. This function is called with lul_request as a string containing the request ID, lul_parameters is a table with all the arguments on the URL, and lul_outputformat is the requested format of the data passed on the &output_format= in the URL.<br />
<br />
You return lul_data which is a string containing the response which is forwarded to the client, and optionally lul_outputformat, which is the document type put in the HTML response.<br />
<br />
==<timed> (callback)==<br />
<br />
variables: lul_data<br />
<br />
return values: none<br />
<br />
When you pass a Lua function to be called at a later time with lu_CallFunctionDelay or lu_CallFunctionTimer (see [[Luup_Lua_extensions]]), the function is called with the data (a string) you past.<br />
<br />
==<watch> (callback)==<br />
<br />
variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new<br />
<br />
return values: none<br />
<br />
When you watch a variable with lu_WatchVariable (see [[Luup_Lua_extensions]]), and the variable changes, your callback is called with the information on the variable that changed.<br />
<br />
==<startup>==<br />
<br />
variables: lul_device<br />
<br />
return values: return 3 variables with the syntax return a,b,c where the first is true if the startup was successful or false if not, followed by 2 strings for the comments and the name of the module<br />
<br />
If this function is called in the startup sequence specified in the 'startup' XML tag, return true if the startup was ok, false if it wasn't, followed by some comments and the name of the module, like this: return false,'Cannot get state','gc100' or return true,'ok','gc100'<br />
<br />
==<ir>==<br />
There are two parts to the equation here:<br />
# the device holding the IR codes for all the different IR commands to be used<br />
# a device associated with 1) above that understands the codes and knows how to emit them via a specific piece of hardware<br />
<br />
In the user interface for device one above, you can select the hardware device, two above, that will emit the IR codes.<br />
<br />
Each <ir> tag hold an IR code - which can be in any format. In device one, you associate the hardware driver (device two). Device number two must understand the format, as found in the <ir> tag and will automagically receive the IR code via the function SendProntoCode(). The hardware is then manipulated to emit the IR code.<br />
<br />
Note that many of the IR devices used by Vera use ProntoCodes, hence the handling function was named SendProntoCode. However this is a misnomer, as the function just receives what is located in the <ir> tag. The hardware driver computer code just needs to emit the IR code it receives, whether that be a prontcode or otherwise.<br />
<br />
Refer to [[Luup_IR]] also.</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Plugins_ByHandLuup Plugins ByHand2014-02-03T23:01:46Z<p>A-lurker: </p>
<hr />
<div>[[Category:Development]]<br />
==Description of the XML files==<br />
<br />
Because the web generator is not yet operational (as of June 2013) you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins (in UI5, choose Apps, Develop Apps, Luup Files). There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click view to see the XML file. We recommend using the Firefox web browser because it has built-in support for nicely displaying XML files in a graphical tree. XML can be hard to read in other web browser.<br />
<br />
When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.<br />
<br />
The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.<br />
<br />
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.<br />
<br />
The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.<br />
<br />
== The Luup XML implementation file ==<br />
<br />
I_GC100.xml is a full featured, functional implementation for the Global Cache GC100, which is an ethernet device with relay switches, input sensors, infrared transmitters and serial ports. I_GC100.xml shows a complete Luup plugin with parent/child devices. The corresponding UPnP Device specification file is D_GC100.xml. In all the implementation files the top-level XML tag (ie root node) is called "implementation". It contains the following elements: <br />
<br />
=== <settings> ===<br />
<br />
The settings node contains various settings for the implementation. <br />
<br />
=== <protocol> ===<br />
Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. The protocol tag tells Luup what's considered a single ''chunk'' of data. By using a format, from the supported list below, you avoid byte-by-byte processing on input streams as the Luup engine will ''chunk'' the data to you and pass it to your Lua code handling <tt><incoming></tt> requests.<br />
Lua code is much cleaner when it handles data in chunks. If you have a protocol that's not natively supported, and is likely to be used by other devices, let us know and we'll add it to the Luup engine so you don't need to mess with it.<br />
<br />
Valid values for this tag are:<br />
*<tt>cr</tt> - all incoming commands are terminated with a carriage return+line character, and all outgoing data should have a cr appended. Incoming data will have the cr stripped off.<br />
*<tt>crlf</tt> - all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. Incoming data will have the cr/lf stripped off.<br />
*<tt>stxetx</tt> - all incoming commands are surrounded by STX and ETX characters. If you send the string "test" the framework will add the STX before and the ETX at the end, and if the string "<tt>''<stx>''test''<etx>''</tt>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.<br />
*<tt>raw</tt> - makes no modifications to outgoing data, and calls your incoming data callback for each byte received. This adds more overhead since the engine needs to call your Luup function for every character, and makes your code complex. So, generally avoid using '<tt>raw</tt>' and let us add support for your protocol if you have a new one we don't yet support.<br />
<br />
=== <ioPort> ===<br />
One way to have the device talk to another device on the internet. If you put a TCP port number here, an outgoing connection attempt is made automatically when the device initializes. (The remote IP address should be placed in the 'ip' box in the device's advanced configuration tab.) If you don't use this tag, you can use luup.io.open(..) instead.<br />
<br />
=== <handleChildren> ===<br />
If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like IR transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.<br />
<br />
=== <functions> ===<br />
Put here the Lua code for functions you want to be able to use in other places in your Lua code. You can also declare local variables here before the functions are described. Watch out for XML syntax. In particular, the > and >= comparison operators should be escaped.<br />
<br />
=== <files> ===<br />
As an alternative to the <functions> element, you can put your Lua implementation in a separate file containing pure Lua code and not worry about XML escapes. By convention, the file name should begin with L_ and end with .lua. as in <files>L_MyDevice.lua</files><br />
<br />
=== <startup> ===<br />
<br />
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag. <br />
<br />
=== <actionList> ===<br />
<br />
This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file. <br />
<br />
==== function declarations ====<br />
<br />
Whatever Lua code you create in the tags will be put inside a function automatically by the Luup engine, and your code will be passed variables that are relevant to whatever the code needs to do. For example, the code inside the 'run' tag is passed lul_device,lul_settings where lul_device is the id of the device the action was sent to, and lul_settings has the arguments to the UPnP action. See: [[Luup Declarations]] for details.<br />
<br />
There are several different nodes you can put within the "action" node. <br />
<br />
==== <run/job/incoming/timeout> ====<br />
<br />
Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want. <br />
<br />
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on. <br />
<br />
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time. <br />
<br />
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values:<br />
<br />
1) the status of the job<br />
<br />
2) how long to wait before the job times out in seconds.<br />
<br />
The status can be:<br />
*0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon. It means it's waiting to start. If you return this value your 'job' code will be run again in the 'timeout' seconds <br />
*2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon. This means the job failed. Your code won't be run again. <br />
*4=job_Done: In vera's UI a job in this state is displayed as a green icon. This means the job finished ok. Your code won't be run again. <br />
*5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon. This means the job is running and you're waiting for return data. Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag. If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run. <br />
<br />
So in the SendProntoCode job code for the I_GC100, we return 5,10 which means we're waiting for data and should wait up to 10 seconds. <br />
<br />
While a job is active, a UPnP control point will see the status of the job when it uses the GetStatus action for Vera. In Vera's web ui an active job is shown as an icon next to the device. While Luup is in the middle of executing the Lua code for your job, the status is reported to the control point as 1=job_InProgress. <br />
<br />
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the I/O port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not. <br />
<br />
The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup Lua extensions]] lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <incoming> ====<br />
<br />
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic I/O port. The Luup engine handles low-level I/O for these types of ports so you only need to call one of the [[Luup Lua extensions]] to send data on the port. <br />
<br />
This is the general purpose incoming data handler that the Luup engine will call whenever data comes in on an I/O Port. It has a node 'lua' which contains the Lua code that is called. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <ir> ====<br />
<br />
Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the [[Luup IO Device]] in the [[Luup Configuration File]]. Note that other formats besides pronto format can be used. Refer to the [[Luup_Declarations#<ir>|IR tag]]<br />
<br />
==Walkthrough to create a device==<br />
<br />
We have documented step-by-step the detailed process of creating a Luup interface, including everything that was done to debug, for Somfy blind control device (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]), which is a simple 1-way serial device (ie send data, but don't get any response) here: [[Luup_Somfy_Walkthrough]]<br />
<br />
No matter type of Luup plugin you'll be creating the procedure to create it is the same. This is true for Luup plugins which do not talk to any external hardware, such as a 'Weather' plugin to provide weather services, as well as plugins that talk to infrared devices like a TV, and serial/network devices.<br />
<br />
The first step is to create the UPnP device specification and service specification files. Whenever possible you will want to re-use existing service specification files, as explained in "Introduction to UPnP" [[Luup_Plugins#Introduction_to_UPnP here]]. And if your device is functionally the same as an existing UPnP Device type, like a light switch, you should re-use an existing UPnP Device Specification file also so that a UPnP Control Point will know how to control your device with modification. A list of all currently known UPnP Device and Service specification files is here: [[Luup_UPNP_Files]]. Find the existing UPnP device file that is most similar to the Luup plugin you are creating, and list all the services that device will implement. Download these files on your computer either from the [[Luup_UPNP_Files]] page, or by going to Vera's setup page and choosing Devices, Luup plugins.<br />
<br />
Modify the UPnP device specification file as needed, such as changing the manufacturer and model. Do not worry about the UDN tag as Luup will create a UDN for you automatically. If your device is functionally the same as the template you started from, leave the deviceType the same. If it's different, modify the deviceType replacing the "schemas-upnp-org" or "micasaverde-com" with some domain that you have, or if you don't have one, just use your name. Leave the :device: but change the word after it to describe your device. The deviceType must contain only a-z, 0-9, : and -.<br />
<br />
In the '''<serviceList>''' tag, add all the services you will implement. You can also create new services at this time. Save your new device file and service files as D_[some name] and S_[some name]. This is not mandated by UPnP, but is a convention we use so it's easy to recognize the file type by the name.<br />
<br />
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.<br />
<br />
To add the device to Vera's configuration file, so Vera will load and use the device, go to the Devices tab and at the bottom fill in the UPnP Device filename in the 'Add Device' box and pick a room. When you save your changes, which causes the Luup engine to reload the new configuration, it will look for the device, service and implementation files to start the device. If the files don't already exist on Vera, Vera will log an error and will not start the device. So you need to upload any new files you created by going to the Devices, Luup plugins page in Vera's setup web page. You can upload several files at once. The files will not be used until the Loop engine is reset so you probably want to check the "Restart Luup after upload" box before you click 'go'. If you upload the files without checking the box and want to restart the Luup engine, just click 'save', even if the button is grayed out. You will likely need to make several changes to the files before they're right so you can leave one web browser open to the 'Luup plugin' page and just modify the files in your text editor then click 'go' again to re-upload them after saving your changes. You can open another browser window or tab to access other pages in Vera's web ui and control the device while leaving the list of files to upload on the Luup plugin page intact so you can re-upload by clicking 'go'.<br />
<br />
Next you'll want to know how to debug your Luup plugins and Lua code. See [[Luup_Debugging]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-03T01:02:04Z<p>A-lurker: </p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, is_ready returns true. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <incoming> block that only processes data if is_ready(lul_device) is true.<br />
<br />
The device parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP 'service' + 'variable' will be set to the 'value' for this device. If there are events or notifications tied to the variable they will be fired. <br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service + variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
The device parameter: if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
Caution 1 - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
Caution 2 - this code is also incorrect:<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tostring only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with os.time to see how long it will be for the next event. luup.sunset-os.time is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to device.<br />
<br />
Pass in the ptr which you received from the luup.chdev.start call. Give each child a unique id so you can keep track of which is which. You can optionally provide a description which the user sees in the user interface.<br />
<br />
device_type is the UPnP device type, such as urn:schemas-upnp-org:device:BinaryLight:1.<br />
<br />
If device_filename is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any device_type you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in implementation_filename. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in implementation_filename.<br />
<br />
If embedded is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The parameters are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a, and = to separate service, variable and value, like this: service,variable=value\nservice...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]], ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred. <br />
<br />
=== function: intercept ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]] <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
== Notes ==<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-03T00:43:26Z<p>A-lurker: /* function: is_ready */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: [[Luup_Lua_extensions#device:_string_or_number|device (string or number)]]<br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, <tt>is_ready</tt> returns <tt>true</tt>. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <tt><incoming> block</tt> that only processes data if <tt>is_ready(lul_device)</tt> is <tt>true</tt>.<br />
<br />
The <tt>device</tt> parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), device (string or number) <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device, which, if it's a string, is interpreted as a udn, and if it's a number, is the device number. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), device (string or number), [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP service+variable will be set to value for device, which if it's a string, is interpreted as a udn, and if it's a number, as a device id. If there are events or notifications tied to the variable they will be fired. <br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), device (string or number) <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service+variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
Caution 1 - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
Caution 2 - this code is also incorrect:<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tostring only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), device (string or number) <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), device (string or number) <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), device (string or number) <br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), device (string or number)<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), device (string or number) <br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with <tt>os.time</tt> to see how long it will be for the next event. <tt>luup.sunset-os.time</tt> is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: device (string or number), ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to <tt>device</tt>. If <tt>device</tt> is a string it is interpreted as a udn, if it's a number, as a device id. <br />
<br />
Pass in the <tt>ptr</tt> which you received from the <tt>luup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a <tt>description</tt> which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as <tt>urn:schemas-upnp-org:device:BinaryLight:1</tt>.<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any <tt>device_type</tt> you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>.<br />
<br />
If <tt>embedded</tt> is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a <tt>,</tt> and <tt>=</tt> to separate service, variable and value, like this: <tt>service,variable=value\nservice</tt>...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: device (string or number), ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
If device is a string it is interpreted as a udn, if it's a number, as a device id. Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: device (string or number), ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), device (string or number) <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred. <br />
<br />
=== function: intercept ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), device (string or number) <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), device (string or number) <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-03T00:37:29Z<p>A-lurker: </p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, <tt>is_ready</tt> returns <tt>true</tt>. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <tt><incoming> block</tt> that only processes data if <tt>is_ready(lul_device)</tt> is <tt>true</tt>.<br />
<br />
The <tt>device</tt> parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), device (string or number) <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device, which, if it's a string, is interpreted as a udn, and if it's a number, is the device number. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), device (string or number), [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP service+variable will be set to value for device, which if it's a string, is interpreted as a udn, and if it's a number, as a device id. If there are events or notifications tied to the variable they will be fired. <br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), device (string or number) <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service+variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
Caution 1 - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
Caution 2 - this code is also incorrect:<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tostring only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), device (string or number) <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), device (string or number) <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), device (string or number) <br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), device (string or number)<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), device (string or number) <br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with <tt>os.time</tt> to see how long it will be for the next event. <tt>luup.sunset-os.time</tt> is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: device (string or number), ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to <tt>device</tt>. If <tt>device</tt> is a string it is interpreted as a udn, if it's a number, as a device id. <br />
<br />
Pass in the <tt>ptr</tt> which you received from the <tt>luup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a <tt>description</tt> which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as <tt>urn:schemas-upnp-org:device:BinaryLight:1</tt>.<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any <tt>device_type</tt> you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>.<br />
<br />
If <tt>embedded</tt> is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a <tt>,</tt> and <tt>=</tt> to separate service, variable and value, like this: <tt>service,variable=value\nservice</tt>...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: device (string or number), ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
If device is a string it is interpreted as a udn, if it's a number, as a device id. Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: device (string or number), ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), device (string or number) <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred. <br />
<br />
=== function: intercept ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), device (string or number) <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), device (string or number) <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job.<br />
<br />
=== device: string or number ===<br />
*If a number, it is the device ID<br />
*If a string, it is the UDN for the UPnP device<br />
Both of these can be found in the User Interface (UI5) under the advanced Tab as "id" and "local_udn" respectively.<br />
<br />
Examples:<br />
<br />
<source lang="lua"><br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency",87)<br />
local update_frequency = luup.variable_get("S_WebcamDropboxUploaderSettings1.xml","SendFrequency","uuid:4d494342-5342-5645-0057-000001c9d682")<br />
</source><br />
<br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-03T00:12:51Z<p>A-lurker: /* function: variable_get */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, <tt>is_ready</tt> returns <tt>true</tt>. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <tt><incoming> block</tt> that only processes data if <tt>is_ready(lul_device)</tt> is <tt>true</tt>.<br />
<br />
The <tt>device</tt> parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), device (string or number) <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device, which, if it's a string, is interpreted as a udn, and if it's a number, is the device number. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), device (string or number), [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP service+variable will be set to value for device, which if it's a string, is interpreted as a udn, and if it's a number, as a device id. If there are events or notifications tied to the variable they will be fired. <br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), device (string or number) <br />
<br />
returns: value (string) and Unix time stamp (number) of when the variable last changed<br />
<br />
Returns the value of the UPnP service+variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
Caution 1 - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
Caution 2 - this code is also incorrect:<br />
<br />
<source lang="lua">local value = tostring(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tostring only expects one<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), device (string or number) <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), device (string or number) <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), device (string or number) <br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), device (string or number)<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), device (string or number) <br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with <tt>os.time</tt> to see how long it will be for the next event. <tt>luup.sunset-os.time</tt> is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: device (string or number), ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to <tt>device</tt>. If <tt>device</tt> is a string it is interpreted as a udn, if it's a number, as a device id. <br />
<br />
Pass in the <tt>ptr</tt> which you received from the <tt>luup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a <tt>description</tt> which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as <tt>urn:schemas-upnp-org:device:BinaryLight:1</tt>.<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any <tt>device_type</tt> you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>.<br />
<br />
If <tt>embedded</tt> is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a <tt>,</tt> and <tt>=</tt> to separate service, variable and value, like this: <tt>service,variable=value\nservice</tt>...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: device (string or number), ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
If device is a string it is interpreted as a udn, if it's a number, as a device id. Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: device (string or number), ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), device (string or number) <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred. <br />
<br />
=== function: intercept ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), device (string or number) <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), device (string or number) <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job. <br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2014-02-02T23:52:28Z<p>A-lurker: /* function: variable_get */</p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, <tt>is_ready</tt> returns <tt>true</tt>. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <tt><incoming> block</tt> that only processes data if <tt>is_ready(lul_device)</tt> is <tt>true</tt>.<br />
<br />
The <tt>device</tt> parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), device (string or number) <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device, which, if it's a string, is interpreted as a udn, and if it's a number, is the device number. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), device (string or number), [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP service+variable will be set to value for device, which if it's a string, is interpreted as a udn, and if it's a number, as a device id. If there are events or notifications tied to the variable they will be fired. <br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), device (string or number) <br />
<br />
returns: value (string) and Unix time stamp (number)<br />
<br />
Returns the value of the UPnP service+variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
Caution - this code is incorrect:<br />
<br />
<source lang="lua">local value = tonumber(luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)))<br />
</source><br />
<br />
luup.variable_get returns two parameters and tonumber also accepts two parameters. However the parameters are incompatible: the Unix timestamp returned by luup.variable_get is being used as a number base in the function tonumber. The number base is limited to a power of 36 or less and the current timestamps are in the range of thousands of millions.<br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), device (string or number) <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), device (string or number) <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- This code logs nil if theDeviceNumber is invalid.<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<source lang="lua">-- This code fails if theDeviceNumber is invalid.<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: ip_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the IP address for a device. This is better than setting the "ip" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: mac_set ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: value (string), device (string or number)<br />
<br />
returns: none<br />
<br />
Sets the mac address for a device. This is better than setting the "mac" attribute using attr_set because it updates internal values additionally, so a reload isn't required.<br />
<br />
=== function: reload ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters: none<br />
<br />
returns: none<br />
<br />
Reloads the Luup engine.<br />
<br />
=== function: create_device ===<br />
<br />
''<span style="color: red">Not available in UI5 or lower</span>''<br />
<br />
parameters:<br />
* device_type (string)<br />
* internal_id (string)<br />
* description (string)<br />
* upnp_file (string)<br />
* upnp_impl (string)<br />
* ip (string)<br />
* mac (string)<br />
* hidden (boolean)<br />
* invisible (boolean)<br />
* parent (number)<br />
* room (number)<br />
* pluginnum (number)<br />
* statevariables (string)<br />
* pnpid (number)<br />
* nochildsync (string)<br />
* aeskey (string)<br />
* reload (boolean)<br />
* nodupid (boolean)<br />
<br />
returns: the device ID<br />
<br />
This creates the device with the parameters given, and returns the device ID.<br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string and content_type it returns will be returned. <br />
<br />
See the Smartphone Web Interface plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
local lul_content_type = "text/html"<br />
return lul_html, lul_content_type<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above.<br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), device (string or number) <br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), device (string or number)<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (int), device (string or number) <br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to 1 if the device is failing, 0 if it's working, and 2 if the device is reachable but there's an authentication error. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with <tt>os.time</tt> to see how long it will be for the next event. <tt>luup.sunset-os.time</tt> is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: device (string or number), ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to <tt>device</tt>. If <tt>device</tt> is a string it is interpreted as a udn, if it's a number, as a device id. <br />
<br />
Pass in the <tt>ptr</tt> which you received from the <tt>luup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a <tt>description</tt> which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as <tt>urn:schemas-upnp-org:device:BinaryLight:1</tt>.<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any <tt>device_type</tt> you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>.<br />
<br />
If <tt>embedded</tt> is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a <tt>,</tt> and <tt>=</tt> to separate service, variable and value, like this: <tt>service,variable=value\nservice</tt>...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: device (string or number), ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
If device is a string it is interpreted as a udn, if it's a number, as a device id. Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: device (string or number), ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), device (string or number) <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred. <br />
<br />
=== function: intercept ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), device (string or number) <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), device (string or number) <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job. <br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DeclarationsLuup Declarations2014-02-02T23:12:53Z<p>A-lurker: /* */</p>
<hr />
<div>[[Category:Development]]<br />
When the Luup engine calls your Lua code it will pass your code some variables relevant to whatever the code is supposed to do. You can see how your code looks with the full function declarations by bringing up this page in a web browser: http://ip_of_vera:49451/data_request?id=lu_lua&DeviceNum=8<br />
<br />
Here is a list of the variables that are passed to your Lua code, and also a list of what return values the Lua code should send back. The tag names are explained here: [[Luup_Plugins_ByHand#The_Luup_XML_implementation_file]]:<br />
<br />
==<run>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. <br />
<br />
return value: true or false where true means the function ran ok, false means it failed. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SetTarget_run(lul_device, lul_settings)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue))<br />
return false -- function failed<br />
<br />
end</source><br />
<br />
==<job>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. lul_job is the id number of the job. <br />
<br />
return value: return 2 values with the syntax return a,b. The first is the job status and is a number from 0-5, and the second is the timeout in seconds.<br />
<br />
For more detail see:<br />
*[[Luup_Lua_extensions#Module:_luup.job|Luup Lua extensions: Module luup.job]]<br />
*[[Luup_Plugins_ByHand#.3Crun.2Fjob.2Fincoming.2Ftimeout.3E|Luup Plugins ByHand: <run/job/incoming/timeout>]]<br />
*[[UI_Notes#Job_status|UI Notes: Job status]]<br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_job(lul_device, lul_settings, lul_job)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job)<br />
-- 5 = job_WaitingForCallback<br />
-- and we'll wait 10 seconds for incoming data<br />
return 5, 10<br />
<br />
end</source><br />
<br />
==<incoming> (returned by a job) ==<br />
<br />
variables: same as for job above, plus lul_data which is a binary string with the data received <br />
<br />
return value: return 3 values with the syntax return a,b,c. The first two are the same as with job, and the 3rd is a true or false indicating if the incoming data was intended for this job. See [[Luup Plugins ByHand#run.2Fjob.2Fincoming.2Ftimeout_this]] for details. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_incoming(lul_device, lul_settings, lul_job, lul_data)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job .. " received data: " .. lul_data)<br />
-- 4 = jobDone<br />
-- nil = n/a on the timeout since the job is done<br />
-- true = the incoming data was for us<br />
return 4, nil, true<br />
<br />
end</source><br />
<br />
==<timeout>==<br />
<br />
variables: same as for job above.<br />
<br />
return value: same as for job above<br />
<br />
==<incoming> (general, not for a job)==<br />
<br />
variables: lul_device is a number that is the device id. lul_data is a binary string with the data received<br />
<br />
return values: none<br />
<br />
==<scene>==<br />
<br />
variables: none<br />
<br />
return values: boolean (true or false)<br />
<br />
This is how the Lua code in a scene is called. The code is ran before the commands in the scene and if the code returns false, the scene is aborted and the command don't run. If anything else is returned, including true or nothing at all, the scene's commands run as normal.<br />
<br />
==<event>==<br />
<br />
variables: lul_value <br />
<br />
return values: boolean (true or false) <br />
<br />
This is how the Lua code in an event is called. The code is run before the event is processed and the commands in the scene are executed. If the code returns false, the event is aborted and the command(s) in the scene&nbsp;will not&nbsp;run. If anything else is returned, including true or nothing at all, the scene's commands run as normal. lul_value is the new value that is being assigned to the variable which the event is monitoring.<br />
<br />
==<request>==<br />
<br />
variables: lul_request,lul_parameters,lul_outputformat<br />
<br />
return values: lul_data,lul_outputformat<br />
<br />
When you call lu_RegisterHandler (see [[Luup_Lua_extensions]]) to handle a request on a URL, you pass in a function name that will handle the request. This function is called with lul_request as a string containing the request ID, lul_parameters is a table with all the arguments on the URL, and lul_outputformat is the requested format of the data passed on the &output_format= in the URL.<br />
<br />
You return lul_data which is a string containing the response which is forwarded to the client, and optionally lul_outputformat, which is the document type put in the HTML response.<br />
<br />
==<timed> (callback)==<br />
<br />
variables: lul_data<br />
<br />
return values: none<br />
<br />
When you pass a Lua function to be called at a later time with lu_CallFunctionDelay or lu_CallFunctionTimer (see [[Luup_Lua_extensions]]), the function is called with the data (a string) you past.<br />
<br />
==<watch> (callback)==<br />
<br />
variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new<br />
<br />
return values: none<br />
<br />
When you watch a variable with lu_WatchVariable (see [[Luup_Lua_extensions]]), and the variable changes, your callback is called with the information on the variable that changed.<br />
<br />
==<startup>==<br />
<br />
variables: lul_device<br />
<br />
return values: return 3 variables with the syntax return a,b,c where the first is true if the startup was successful or false if not, followed by 2 strings for the comments and the name of the module<br />
<br />
If this function is called in the startup sequence specified in the 'startup' XML tag, return true if the startup was ok, false if it wasn't, followed by some comments and the name of the module, like this: return false,'Cannot get state','gc100' or return true,'ok','gc100'<br />
<br />
==<ir>==<br />
There are two parts to the equation here:<br />
# the device holding the IR codes for all the different IR commands to be used<br />
# a device associated with 1) above that understands the codes and knows how to emit them via a specific piece of hardware<br />
<br />
In the user interface for device one above, you can select the hardware device, two above, that will emit the IR codes.<br />
<br />
Each <ir> tag hold an IR code - which can be in any format. In device one, you associate the hardware driver (device two). Device number two must understand the format, as found in the <ir> tag and will automagically receive the IR code via the function SendProntoCode(). The hardware is then manipulated to emit the IR code.<br />
<br />
Note that many of the IR devices used by Vera use ProntoCodes, hence the handling function was named SendProntoCode. However this is a misnomer, as the function just receives what is located in the <ir> tag. The hardware driver computer code just needs to emit the IR code it receives, whether that be a prontcode or otherwise.<br />
<br />
Refer to [[Luup_IR]] also.</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DeclarationsLuup Declarations2014-02-02T22:18:54Z<p>A-lurker: </p>
<hr />
<div>[[Category:Development]]<br />
When the Luup engine calls your Lua code it will pass your code some variables relevant to whatever the code is supposed to do. You can see how your code looks with the full function declarations by bringing up this page in a web browser: http://ip_of_vera:49451/data_request?id=lu_lua&DeviceNum=8<br />
<br />
Here is a list of the variables that are passed to your Lua code, and also a list of what return values the Lua code should send back. The tag names are explained here: [[Luup_Plugins_ByHand#The_Luup_XML_implementation_file]]:<br />
<br />
==<run>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. <br />
<br />
return value: true or false where true means the function ran ok, false means it failed. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SetTarget_run(lul_device, lul_settings)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue))<br />
return false -- function failed<br />
<br />
end</source><br />
<br />
==<job>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. lul_job is the id number of the job. <br />
<br />
return value: return 2 values with the syntax return a,b. The first is the job status and is a number from 0-5, and the second is the timeout in seconds.<br />
<br />
For more detail see:<br />
*[[Luup_Lua_extensions#Module:_luup.job|Luup Lua extensions: Module luup.job]]<br />
*[[Luup_Plugins_ByHand#.3Crun.2Fjob.2Fincoming.2Ftimeout.3E|Luup Plugins ByHand: <run/job/incoming/timeout>]]<br />
*[[UI_Notes#Job_status|UI Notes: Job status]]<br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_job(lul_device, lul_settings, lul_job)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job)<br />
-- 5 = job_WaitingForCallback<br />
-- and we'll wait 10 seconds for incoming data<br />
return 5, 10<br />
<br />
end</source><br />
<br />
==<incoming> (returned by a job) ==<br />
<br />
variables: same as for job above, plus lul_data which is a binary string with the data received <br />
<br />
return value: return 3 values with the syntax return a,b,c. The first two are the same as with job, and the 3rd is a true or false indicating if the incoming data was intended for this job. See [[Luup Plugins ByHand#run.2Fjob.2Fincoming.2Ftimeout_this]] for details. <br />
<br />
Sample; including the function/end lines, which the Luup engine '''adds automatically''':<br><br />
<br />
<source lang="lua">function SendProntoCode_incoming(lul_device, lul_settings, lul_job, lul_data)<br />
<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job .. " received data: " .. lul_data)<br />
-- 4 = jobDone<br />
-- nil = n/a on the timeout since the job is done<br />
-- true = the incoming data was for us<br />
return 4, nil, true<br />
<br />
end</source><br />
<br />
==<timeout>==<br />
<br />
variables: same as for job above.<br />
<br />
return value: same as for job above<br />
<br />
==<incoming> (general, not for a job)==<br />
<br />
variables: lul_device is a number that is the device id. lul_data is a binary string with the data received<br />
<br />
return values: none<br />
<br />
==<scene>==<br />
<br />
variables: none<br />
<br />
return values: boolean (true or false)<br />
<br />
This is how the Lua code in a scene is called. The code is ran before the commands in the scene and if the code returns false, the scene is aborted and the command don't run. If anything else is returned, including true or nothing at all, the scene's commands run as normal.<br />
<br />
==<event>==<br />
<br />
variables: lul_value <br />
<br />
return values: boolean (true or false) <br />
<br />
This is how the Lua code in an event is called. The code is run before the event is processed and the commands in the scene are executed. If the code returns false, the event is aborted and the command(s) in the scene&nbsp;will not&nbsp;run. If anything else is returned, including true or nothing at all, the scene's commands run as normal. lul_value is the new value that is being assigned to the variable which the event is monitoring.<br />
<br />
==<request>==<br />
<br />
variables: lul_request,lul_parameters,lul_outputformat<br />
<br />
return values: lul_data,lul_outputformat<br />
<br />
When you call lu_RegisterHandler (see [[Luup_Lua_extensions]]) to handle a request on a URL, you pass in a function name that will handle the request. This function is called with lul_request as a string containing the request ID, lul_parameters is a table with all the arguments on the URL, and lul_outputformat is the requested format of the data passed on the &output_format= in the URL.<br />
<br />
You return lul_data which is a string containing the response which is forwarded to the client, and optionally lul_outputformat, which is the document type put in the HTML response.<br />
<br />
==<timed> (callback)==<br />
<br />
variables: lul_data<br />
<br />
return values: none<br />
<br />
When you pass a Lua function to be called at a later time with lu_CallFunctionDelay or lu_CallFunctionTimer (see [[Luup_Lua_extensions]]), the function is called with the data (a string) you past.<br />
<br />
==<watch> (callback)==<br />
<br />
variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new<br />
<br />
return values: none<br />
<br />
When you watch a variable with lu_WatchVariable (see [[Luup_Lua_extensions]]), and the variable changes, your callback is called with the information on the variable that changed.<br />
<br />
==<startup>==<br />
<br />
variables: lul_device<br />
<br />
return values: return 3 variables with the syntax return a,b,c where the first is true if the startup was successful or false if not, followed by 2 strings for the comments and the name of the module<br />
<br />
If this function is called in the startup sequence specified in the 'startup' XML tag, return true if the startup was ok, false if it wasn't, followed by some comments and the name of the module, like this: return false,'Cannot get state','gc100' or return true,'ok','gc100'<br />
<br />
==<ir>==<br />
<br />
A pronto code is placed between the <ir> tags. Executing the action will transmit the ProntoCode via the associated IR device. Refer to [[Luup_IR]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Somfy_WalkthroughLuup Somfy Walkthrough2014-02-02T21:55:40Z<p>A-lurker: /* Step 4: Test the port */</p>
<hr />
<div>[[Category:Development]]<br />
This page is for developers wanting to learn how to write a plugin. If you simply want to use the Somfy plugin see: [[Somfy_Plugin]]<br />
<br />
This page walks through in detail the process of creating a Luup plugin to control the Somfy RS232 interface for motorized blinds as a sample. The Somfy documentation (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]) explains that you talk to the controller using RS232 9600 Baud, 8 Data Bits, 1 Stop Bit, No Parity. The interface supports up to 16 blinds. The communication is one-way only; there are no response codes or incoming data from the blinds when they change manually. Here is the relevant section from the manual.<br />
<br />
RS232 Operation<br />
1. Set RS232 communication settings to: 9600 Baud, 8 Data Bits, 1 Stop Bit, No Parity<br />
2. Use the ASCII protocol command string syntax: <!> <2 digit channel number> <Directional Command><br />
3. The directional commands must be capital letters and are as follows:<br />
U: UP<br />
D: DOWN<br />
S: STOP<br />
4. Examples: Motor 2 Up: !02U<br />
Motor 5 Down: !05D<br />
Motor 1 Stop: !01S<br />
<br />
==Step 1: SSH into Vera==<br />
<br />
Since this device is so simple it may not be necessary to debug anything. But we'll do this anyway. You need to give Vera a root password, which is the administrator password to login directly. Do this by either: a) from a command prompt in Windows, Linux or Mac type telnet [ip address of vera] and when you see the ''root@HomeControl:~#'' prompt enter: ''passwd'' and type in a password twice. Then type exit. Or b) In Vera's web ui click Advanced, Net & Wi-fi and the 'Advanced Configuration' link, and supply the password there. Note, sometimes Windows Vista doesn't have the telnet utility installed, so you have to use step b.<br />
<br />
Once you have a root password set, you will no longer be able to use telnet to login to Vera, you must use ssh. The reason you should use ssh to login for debugging, and not telnet, is because some of the useful key sequences, like Ctrl+C, don't work in telnet.<br />
<br />
Once you've setup a root password, from a Mac or Linux console type: ''ssh [ip of vera]'' From a Windows PC, download putty.exe here: [http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html]. You don't need to install putty, just put the .exe on your desktop or in a folder. When you run putty.exe, type in Vera's IP address and click 'open'. If you don't see the root password or need help logging in see [[Logon_Vera_SSH#Can.27t_find_the_root_password.3F|Logon # Can't find the root password?]].<br />
<br />
When you see ''login as:'', enter: ''root'' and then enter your password. Now from the ''root@HomeControl:#'' prompt type:<br />
<br />
cd /var/log/cmh<br />
tail -f LuaUPnP.log<br />
<br />
Do something in Vera's web ui and you'll see the logs go. Whenever the system rotates the logs, meaning purges the log files because Vera's memory is limited for storing logs, your tail will end. Press the 'up' arrow and hit 'enter' to display the tail command again and restart it. Press Ctrl + c when you've confirm the logs are working ok.<br />
<br />
In Vera's web ui, choose Advanced, Logs, and check 'Verbose logging'. This option will create large logs that have lots of information we may need for debugging. It will stay checked for 24 hours and then automatically turn itself off so you're not creating unnecessarily large log files.<br />
<br />
==Step 2a: Add your device with the web generator tool==<br />
<br />
--coming soon. for now use step 2b.<br />
<br />
== Step 2b: Add your device by building the XML files by hand ==<br />
<br />
To do this you'll want a good text editor. I'm using, such as Notepad++ available here: [http://notepad-plus.sourceforge.net/uk/site.htm] by clicking Download, Download Notepad++ executable files, download and run the npp....Installer.exe file, and you may also want to download the 'XML plugin' from the download page, and unzip it putting the file \Program Files\Notepad++\plugins. <br />
<br />
In Vera's setup UI, go to Devices, Luup plugins, click 'Luup files' and download the files: <br />
<br />
''D_BinaryLight1.xml'' since there is no UPnP device specification for blinds, and blinds are essentially binary devices that are either up/down, we can implement the up/down using the same service that a light switch uses, and that way any UPnP control point that can control a light switch, will also be able to control blinds. <br />
<br />
''S_SwitchPower1.xml'' because when you open D_BinaryLight1.xml, you'll see that in the 'services' section, this is the filename (SCPDURL) for the SwitchPower service. <br />
<br />
[[Media:D_TestSerial.zip | ''D_TestSerial.xml'']] because this is a sample serial device we can use as a template. <br />
<br />
[[Media:I_TestSerial.zip | ''I_TestSerial.xml'']] because this is a sample implementation for our serial device. <br />
<br />
Open D_TestSerial.xml in your text editor and re-save it as a different name, such as D_SomfyBlinds.xml. Change the xml field deviceType to "urn:somfy-com:device:blinds:1" or use your own domain name instead of somfy-com. Change friendlyName to "Somfy Blind Controller", manufacturer to "Somfy", manufacturerURL to "somfy.com", modelDescription to "16 port RS232 to Somfy blind interface", modelName to "1810686" (the Somfy part number). modelURL and serialNumber and UPC aren't really important so you can just remove them. Add in their place a protocol tag with the value 'raw', and handlechildren (we'll explain this one later) with the value 1, like this: <br />
<br />
<source lang="xml"><br />
<protocol>raw</protocol><br />
<handleChildren>1</handleChildren><br />
</source><br />
<br />
The 'raw' protocol is because the Somfy device doesn't have any particular low level protocol, like terminating blocks of data with a carriage return, etc. Remove all the tags in 'servicelist'. <br />
<br />
Change the filename in the ''implementationFile'' tag from I_TestSerial.xml to I_SomfyBlinds.xml. <br />
<br />
Open I_TestSerial.xml and delete the sample Lua code within the 'actionList' tag. We have to put some Lua code in the implementation file for the Luup engine to start it, so for now just put a placeholder in the 'functions' xml tag: <br />
<br />
<source lang="lua"><br />
function lug_startup(lul_device)<br />
luup.log("Somfy blind #" .. lul_device .. " starting up with ID " .. lug_device[lul_device].ID)<br />
end<br />
</source><br />
<br />
and in the 'startup' xml tag, put: ''lug_startup'', like this: <br />
<br />
<source lang="xml"><br />
<startup>lug_startup</startup><br />
</source><br />
<br />
So we've created a Lua function, lug_startup, told the Luup engine to call it when the engine is starting up. The name lug_startup is arbitrary, it can be anything you want. Save the file as I_SomfyBlinds.xml. <br />
<br />
Back on Vera's setup ui under 'Luup files' you the first 'upload' button to upload D_SomfyBlinds.xml and the 2nd to upload I_SomfyBlinds.xml. Check ''Restart Luup after upload'' because the files won't be processed unless we restart the Luup engine, and click 'go'. Now leave your browser tab open at this page so that as we change the implementation file we can just click 'go' without having to select the files again. <br />
<br />
In another browser tab go to Vera's device page again and under 'Add device' put the new device filename ''D_SomfyBlinds.xml'' in the input box, and choose the room where the blind controller is located, click 'Add device'. You'll have a new, blank device in that room, so give it a description "Somfy Blind Controller", then click 'save'. <br />
<br />
Note that if the Somfy device was bi-direction we should attempt to communicate with it during the startup sequence and the startup function should return false if the sequence failed, or true if it succeeded, followed by some comments and the name of the module, which information is shown to the user in the info panel. So: return false,'Interface not responding','Somfy Blind' or return true,'Initialized OK','Somfy Blind'<br />
<br />
==Step 3: Setup the port==<br />
<br />
Vera talks to serial devices using a serial port proxy that turns the serial port into a network port. This way the serial port can reside anywhere on the network. Here are 3 ways to connect Vera to the Somfy device:<br />
<br />
1. Get the UC232R-10 usb->serial adapter here: [http://www.futureelectronics.com/en/Pages/index.aspx] Connect it to one of Vera's USB ports. In Vera's web ui, click the 'save' button, which, even if the 'save' button is grayed out, causes the Luup engine to re-initialize and scan for new serial ports. Wait 30 seconds.<br />
<br />
2. Use any Windows COM port or USB->serial port device. Download [[Windows_Serial_Proxy]] and run it as explained there. Wait 30 seconds after running it and it will exit after it has reported the port to the Luup engine.<br />
<br />
3. Connect a Global Cache GC-100 to your network. Wait 30 seconds or so for the Luup engine to find it. This should happen automatically. Bring up the GC100's setup page in your web browser; you can find the IP address in Vera's device page under the + sign, and set the baud/parity/etc. for the port to match. In the case of the GC-100, this information is supplied to the GC-100 itself and the values in the Serial Port Configuration page have no effect.<br />
<br />
After you've done 1, 2 or 3, go to Devices, Luup plugins and click "Serial port configuration". The serial port should be on the list. Use the pull-down's to set the serial port options: baud=9600, Data Bits=8, Stop Bits=1, Parity=none. For the 'Use with device', select your Somfy Blind controller. Choose 'Save'.<br />
<br />
==Step 4: Test the port==<br />
<br />
First, make note of the device id for your Somfy blinds by clicking '+' next to the device in the device list. Next, go to Devices, Luup Plugins, Test Luup code. In the 'Device number' input box put the device number for the Somfy blinds. This way whatever code we test uses the Lua instance for the Somfy blinds, which will be configured already to use the serial port.<br />
<br />
In the 'Code' input box, type: ''luup.io.write('!01U')'' and click 'Go'. That is a command according to the Somfy specs, which should make motor #1 go up. You can try other commands and click 'Go' each time. If everything is working ok, skip to step 6. If it's not working, you will want to do some debugging. Here's some debug things to try:<br />
<br />
1. Be sure you checked 'Verbose logging' in Step 1. In your ssh console (ie putty if you're using windows) enter ''tail -f LuaUPnP.log | grep '^5\|^01' '' where the | means to send the output through the grep utility, which will filter out only certain lines. The ^ means 'lines that start with'. Line that start with 5 are logs related to Lua, and lines that start with 01 are critical errors. The \| means 'or' for grep. Now go back and click 'go' again in the Test Luup window. Return to the ssh console and you should see a line that starts with 51, which means data sent within Luup, that shows !01U, in this format: ''51 06/29/09 17:19:53.453 0x21 0x30 0x31 0x55 0xd 0xa (!01U\r\n)'', where the human-readable ascii text is in () at the end, following the binary/hex. If you're going to be switching back and forth between the ssh console and web ui to do tests, then before you switch to the web ui, you can either hit 'enter' a few times in the ssh console to add some blank space, or press Ctrl+c and then 'up' followed by 'enter' to restart tail. That way you've created some separation between existing log entries and new ones so you can clearly see what is happening when you click 'go'. If you're not seeing anything, in the Test Luup window add the line ''luup.log('test somfy')'' above the luup.io.write. You should see 'test somfy' get logged when you press 'go'.<br />
<br />
2. Assuming you do see the line '51' log entry showing that the Luup engine is trying to send data, you may want to check the serial port itself. You can go back to the 'serial port configuration' and remove the 'Somfy blind' device from the serial port so the Luup engine won't open the port. Make a note of the network ip and port for the serial port. Click 'Save' to save your changes. Now from a command prompt run: ''telnet [ip] [port]''. If you don't have telnet, open another putty session and click the 'telnet' radio box, put in the ip address and port and click 'open'. You should now be able to type the commands in the telnet session: ''!01U'' and see the blinds work. If it still doesn't work, try connecting the blinds directly to your PC and using a terminal program, like Hyperterminal, to talk directly to the serial port and confirm the connections are ok.<br />
<br />
==Step 5: Create the child devices for the blinds ==<br />
<br />
In Luup, when you have an interface device which is able to control multiple devices this is represented as a tree where the interface is a 'virtual' device, that probably doesn't implement any actions itself, and multiple 'child devices' under the interface device for each device the interface can control. The Somfy Blind controller is such a device because the 1 interface can control 16 blinds. If it only controlled 1 blind, we wouldn't need to implement the parent/multiple child architecture, because we would have just 1 device which implemented the blind functions. We also wouldn't need to create any functions in Lua and we'd simply have put the control protocol in the 'action' tags in the implementation XML. But we want a parent 'Somfy interface' with up to 16 different child devices for each of the 16 blinds the device can control. This means it's a bit more complicated, so we'll want to use Lua functions, like 'lug_startup'.<br />
<br />
The way to implement multiple children is with the 'Reporting child devices' commands documented in [[Luup_Lua_extensions]]. This is how a parent device, in this case the Somfy blind interface, reports to the Luup engine what child devices it has and what ID number it will use internally to keep track of each one. Every device in Luup has an "ID" value which has no meaning to the Luup engine itself, but which is used by parent devices to keep track of the children. Many interface devices have a way to get from them the list of child devices so the parent can manage the children automatically. The Somfy doesn't. So we have 2 choices: 1) Just automatically report 16 child devices since the interface supports 16 devices, or 2) Let the user indicate which Somfy blind numbers he has and report just those child devices. The advantage of #1 is that it's very simple, but the drawback is that the user will see 16 blinds in his home, even if he only has 1. So, we will do it with #2. The first question is where to let the user store the list of blind numbers that are active. The usual way to store this information is to create a UPnP service which contains variables for all the various parameters the user should specify to configure the device. Creating a UPnP service description document and adding it to the device description is a lot of work to get only 1 parameter: a list of device numbers. But, what we can do is instead just create a variable in Lua using some service id/variable we made up, like this:<br />
<br />
<source lang="lua"><br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)<br />
</source><br />
<br />
This won't be an 'official' UPnP variable and won't show up in a UPnP scanner. But, it does show up in Vera's user interface as a variable the user can edit the value. We want to be sure we set this to a default value if it's not already there so the user sees it in the UI and can change it.<br />
<br />
Now we'll add the Lua code to iterate through all the blinds and create the devices, which will go in the lug_startup function we created earlier. We could put the code directly in the XML implementation file. But that means saving/uploading the file each time, and is a bit more tedious. It's often easier to debug it first in the 'Test Luup code' window. So we'll this to test first. First, put in the code box: ''lug_startup(123)'' but change the 123 to the actual device number of the Somfy blind, and click 'go'. Switch back to the ssh console that is tail'ing the LuaUPnP.log. You'll see that it logged ''Somfy blind #... starting up with ID''. This is because we already created the lug_startup function in the implementation file, and in the code box we just called that function. Now put this in the code box:<br />
<br />
<source lang="lua"><br />
function lug_startup(lul_device)<br />
local lul_ID="01,02"<br />
<br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1","BlindIds", lul_ID, lul_device)<br />
luup.log("test2--Somfy blind #" .. lul_device .. " starting up with ID " .. lul_ID)<br />
end<br />
<br />
lug_startup(123)<br />
</source><br />
<br />
where 123 should be your actual device number for the device. The Lua code in ''function lug_startup'' will cause the lug_startup function that was already in the implementation file to be replaced with this version, without actually executing the function (just updating it), so afterward we also have lug_startup(123) to actually run the new version we updated. When you click 'go' you'll see in the ssh log that the it now says ''test2--Somfy blind'', so you can see the lug_startup function was replaced. Note that any functions you create in the 'Test Luup Code' window will still stay in the Luup engine until you reload the Luup engine. So if you type in the code box:<br />
<br />
<source lang="lua"><br />
function some_test()<br />
luup.log("some_test")<br />
end<br />
</source><br />
<br />
click 'go', nothing will get logged. If you then erase the code box and type: ''some_test()'' when you click 'go', you'll see "some_test" in the log. If you want to update the some_test function, and run it at the same time, do:<br />
<br />
<source lang="lua"><br />
function some_test()<br />
luup.log("some_test2")<br />
end<br />
<br />
some_test()<br />
</source><br />
<br />
Now when you click 'go', you'll see "some_test2" be logged. So we'll create a simple startup that creates all 16 blinds by putting this in the Test Luup code window:<br />
<br />
<source lang="lua"><br />
function lug_startup(lul_device)<br />
child_devices = luup.chdev.start(lul_device);<br />
for i = 1,16 do<br />
s = string.format("%02d", i)<br />
luup.log("Adding blind " .. s)<br />
luup.chdev.append(lul_device, child_devices, s, "Blind #" .. s, "urn:schemas-upnp-org:device:BinaryLight:1", "D_BinaryLight1.xml", "", "", true)<br />
end<br />
<br />
luup.chdev.sync(lul_device, child_devices)<br />
end<br />
<br />
lug_startup(123)<br />
</source><br />
<br />
The ''for i=1,16'' is runs the block before 'end' 16 times, assigning the variable 'i' to 1 to 16 each time. We want the devices to be 0 padded to 2 digits, so 1 should be "01". This is because the ID which the Somfy needs is a zero-padded 2 digit number. It also will make it easier to filter just the blinds we want to control as explained later. The statement ''s = string.format("%02d", i)'' converts the numeric value 'i' to a string that is padded to decimal places. This is documented in Lua (see [http://www.lua.org/pil/20.html#StringLib]).<br />
<br />
luup.chdev.start() takes as a parameter our device id (ie the parent device) and it tells the Luup engine we're going to start listing all our children. It returns a handle which we pass in to to the other chdev_ functions. luup.chdev.append() adds each child and it takes the parent device number, the handle from lu_chdev_start, the id the parent will use to identify the device (s=01, 02, etc.), and the device type. This is a UPnP device. We'll use the existing UPnP standard "Binary light" because this way every UPnP control point will treat the blinds as a light and show the user an 'on' and 'off' button. We could create our own device type for blinds which had actions "open" and "close", but then the UPnP Control Points (like the iPhone interface) would need to be updated to know how to present the user with blinds. luup.chdev.sync() is called when we're done to synchronize the child device list with Luup's database, and create/remove any new/missing devices, and restart the Luup engine if devices were added or removed. See: [[Luup_Lua_extensions]]<br />
<br />
Before you click 'go', restart your tail in the ssh window with: ''tail -f LuaUPnP.log | grep '^5\|^01\|^03\|^09\|^11' '' because the 03 logs indicate when the Luup engine stops and restart, the 09 logs show us when devices are created/deleted, and the 11 logs show when the child device functions are calls. Now when you click 'go', you should see in the logs ''Child_Devices::AddChild'' is called 16 times, and then 16 times ''Child_Devices::ProcessChildDevice created device''. You should also see lines starting with '03' that show the Luup engine is reloading since new devices were just created. When it restarts, you'll see 16 new devices in the logs starting with 09.<br />
<br />
Now if you click 'go' again, you'll see 16 logs for ''Child_Devices::AddChild'', but no devices will be created or removed because the 16 devices were already created, and the parent/child device lists are still in sync. We have the same 16 child devices with no new ones or removed ones.<br />
<br />
Now if you refresh Vera's web UI, you'll see the Somfy device has '16' embedded devices, each with "on" and "Off". The 'true' at the end of the luup.chdev.append means the child devices are 'embedded'. That means when they're shown in Vera's web UI, they're all grouped together as one compound device. If you changed the 'true' to a 'false', all 16 blinds would show up as completely separate devices. This is really just a cosmetic difference. The advantage to using 'false' (non-embedded) is that the user can put each blind in a different room. In this case it's probably better to treat the devices as non-embedded, since it is likely the 16 blinds will be scattered around the house. So change the true at the end of luup.chdev.append to a false. Then click 'go'. Now, the 16 blinds are removed, and re-added as non-embedded devices. So, if you refresh Vera's web UI and go to devices, you'll 16 different blinds that you can put into different rooms, in addition to the "Somfy blind interface" device.<br />
<br />
Next, we'll update our Lua code to only create child devices that are specified in the "ID" parameter of the "Somfy blind interface" so that if the user doesn't have 16 devices he can specify just the ones he does have. To make our coding easier, we'll document in the notes for the user that when specifying the list of blinds in the "ID" he must use 01,02, etc., rather than 1,2, etc. because by storing all child devices as 2 digits, we can do a simple search without having to actually parse the ID. In other words, if we want to know if blind #1 is active, we can safely search the ID for 01 and know that it will only match if blind #1 is active. If the user stored single digit blind numbers, like ''5,6,11'' then searching for '1' would give us a false positive and we'd need to write more Lua code to break the list apart. This also makes it easier to send the blind commands because the Somfy protocol says the blind number must be 2 digits, so we won't need to pad the ID's with 0 for the Somfy device. This is why we did the for loop with 16 text strings using string.format which would have given us normal, non-zero-padded numbers.<br />
<br />
Put a -- in front of the luup.chdev.append in the window and click 'go' again. -- means "comments" in Lua, and lines starting with -- are ignored. Therefore, by eliminating the luup.chdev.append, now the luup.chdev.sync will remove all 16 children since none were appended. This way we can reload Vera's web UI, go to the devices page, and we won't have those 16 blinds anymore requiring us to provide a room. Click '+' next to the Somfy blind interface, click 'Advanced' and in the BlindIds enter: '02,07,13', click 'save'. Now in the 'Test Luup code' window enter:<br />
<br />
<source lang="lua"><br />
function lug_startup(lul_device)<br />
local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_device)<br />
if (lul_ID == nil) then<br />
lul_ID = "01,02"<br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)<br />
end<br />
<br />
local lul_prefix = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_device)<br />
if (lul_prefix == nil) then<br />
lul_prefix = "01"<br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_prefix, lul_device)<br />
end<br />
<br />
luup.log("Somfy ID is " .. lul_ID .. " prefix is " .. lul_prefix)<br />
child_devices = luup.chdev.start(lul_device);<br />
for i = 1,16 do<br />
s = string.format("%02d", i)<br />
if (string.find(lul_ID,s) ~= nil) then<br />
luup.log("Adding blind " .. s)<br />
luup.chdev.append(lul_device, child_devices, s, "Blind #" .. s, "urn:schemas-upnp-org:device:BinaryLight:1", "D_BinaryLight1.xml", "", "", false)<br />
end<br />
end<br />
<br />
luup.chdev.sync(lul_device, child_devices)<br />
end<br />
<br />
lug_startup(123)<br />
</source><br />
<br />
Don't forget to change the '123' to your actual device number. Now when you click 'go' you'll see the logs indicate we only have 3 blinds with ID's 02, 07 and 13. Since this part of the code is done, you can copy/paste this from the Luup test code window into the implementation. '''IMPORTANT:''' Be sure the ''lug_startup(123)'' is NOT in your implementation file, or else your lug_startup function will be called twice at each startup: once by the Luup engine with the actual device number, and once when the startup functions are loaded into the Lua engine with your hard-coded number used for testing, which won't necessarily correspond to the real device number. We also add the variable lul_prefix because the URTS version 1 takes an ! in the front of every command, whereas with version 2 it's a 2 digit number from 01 to 16. So we'll default to 01, the default value for an URTS version 2, but we'll let the user change it. We'll store this in a global variable, lul_prefix, so it's available for use in the other functions in our plugin.<br />
<br />
If you're editing the XML file by hand, you can save your changes and click 'go' in the Luup files upload page in your web browser to upload your changes.<br />
<br />
Every time you re-start the Luup engine, such as clicking 'Save', you'll see in the logs this startup sequence.<br />
<br />
==Step 5b: Polish up the startup sequence ==<br />
<br />
The the startup sequence as it is the user will always think the Somfy module loaded fine, even when it didn't, giving the user a false sense that everything was ok even when he hadn't yet specified basic parameters.<br />
<br />
Now, the code first checks for basic startup parameters, which are stored in UPNP variables, and sets them to a default value if they're not already specified. You should at least set them an empty string because by setting them to something then the user will see them in the web user interface and be able to easily change them, as opposed to creating them again from scratch:<br />
<br />
<source lang="lua"><br />
local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_device)<br />
if (lul_ID = nil) then<br />
lul_ID = "01,02"<br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)<br />
end<br />
</source><br />
<br />
Next, since this device uses an IO Port (ie a serial port, or an ethernet port, or a usb connection), we should check that the connection is specified and is active before doing the startup:<br />
<br />
<source lang="lua"><br />
if (luup.io.is_connected(lul_device) == false) then<br />
luup.log('No port for Somfy', 1)<br />
luup.task('Choose the Serial Port for the URTSI', 2, 'Somfy Blind Interface', -1)<br />
return false<br />
end<br />
</source><br />
<br />
If there's a failure of any kind, we should call luup.task and put in some sort of description with the status code 2 (Error), and then 'return false'. This way the user sees in the web panel that the module 'Somfy Blind Interface' is in an error condition and what the problem is (he didn't choose the serial port). If we didn't do the 'return false', the user would think the Somfy module was ok since, when the startup function doesn't return false, the architecture assumes the module is running fine. If we called return false, didn't call luup.task, then the user see that the module was failing, but would have only a generic "Startup sequence failed" without knowing the explicit reason.<br />
<br />
So now the startup sequence looks like this:<br />
<br />
<source lang="lua"><br />
function lug_startup(lul_device)<br />
local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_device)<br />
if (lul_ID == nil) then<br />
lul_ID = "01,02"<br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)<br />
end<br />
<br />
local lul_prefix = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_device)<br />
if (lul_prefix == nil) then<br />
lul_prefix = "01"<br />
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_prefix, lul_device)<br />
end<br />
<br />
luup.log("Somfy ID is " .. lul_ID .. " prefix is " .. lul_prefix)<br />
<br />
if (luup.io.is_connected(lul_device) == false) then<br />
luup.log('No port for Somfy', 1)<br />
luup.task('Choose the Serial Port for the URTSI',2,'Somfy Blind Interface', -1)<br />
return false<br />
end<br />
<br />
child_devices = luup.chdev.start(lul_device);<br />
for i = 1,16 do<br />
s = string.format("%02d", i)<br />
if (string.find (lul_ID,s) ~= nil) then<br />
luup.log("Adding blind " .. s)<br />
luup.chdev.append(lul_device, child_devices, s, "Blind #" .. s, <br />
"urn:schemas-micasaverde-com:device:WindowCovering:1",<br />
"D_WindowCovering1.xml", "", "", false)<br />
end<br />
end<br />
<br />
luup.chdev.sync(lul_device, child_devices)<br />
end<br />
</source><br />
<br />
==Step 6a: Implementation the actions in the web generator ==<br />
<br />
--coming soon--<br />
<br />
==Step 6b: Implementation the actions in the xml file ==<br />
<br />
The reason we added the ''<handleChildren>1</handleChildren>'' tag to the Somfy blind interface's device file earlier is because we're going to put all the actions for the child devices (the blinds) within the main implementation file for the Somfy. So, whenever an action comes in for a child device (a blind), the Lua code in the parent devices implementation file will handle it. Replace the actionList tag in the implementation file with this:<br />
<br />
<source lang="xml"><br />
<actionList><br />
<action><br />
<serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId><br />
<name>SetTarget</name><br />
<run><br />
local lul_command = lul_prefix .. luup.devices[lul_device].id .. 'U\r'<br />
local lul_reverse = luup.variable_get("urn:micasaverde-com:serviceId:HaDevice1", "ReverseOnOff", lul_device)<br />
<br />
if (lul_settings.newTargetValue == "1" or (lul_settings.newTargetValue == "0" and lul_reverse == "1")) then<br />
lul_command = lul_prefix .. luup.devices[lul_device].id .. 'D\r'<br />
end<br />
<br />
if (luup.io.write(lul_command) == false) then<br />
luup.log("cannot send: " .. tostring(lul_command),1)<br />
luup.set_failure(true)<br />
return false<br />
end<br />
</run><br />
</action><br />
</actionList><br />
</source><br />
<br />
This provides the implementation or the SetTarget UPnP action, which is what is sent when the user clicks 'on' or 'off' for a light switch, or 'open' or 'close' for blinds. We store the command to send in the variable lul_command and put the 'local' keyword in front so the variable is only available while the 'SetTarget' action is running. We also check the HADevice service's ReverseOnOff value. As a convention in Luup, we created this as a common service that all home automation devices have, and the ReverseOnOff variable, if true, means reverse the usual on/off behavior, so on is off and off is on. We did this because it's not uncommon for binary switches, particularly blinds, to be wired the wrong way, or for it to be subjective which position is on vs. off. This way the user can add this variable for his configuration if the operation is backwards from what he would expect. lu_SetCommFailure sets a "communication failure" flag for the device, which logs a critical error, and allows the user to see that the device is having problems. Whenever the Luup engine reloads, such as clicking 'save', the flag is cleared again.<br />
<br />
Now when you click 'on' or 'off' for the blinds you'll see in the log it is sending the corresponding data to the blinds.</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DeclarationsLuup Declarations2014-02-02T21:54:35Z<p>A-lurker: /* (returned by a job) */</p>
<hr />
<div>[[Category:Development]]<br />
When the Luup engine calls your Lua code it will pass your code some variables relevant to whatever the code is supposed to do. You can see how your code looks with the full function declarations by bringing up this page in a web browser: http://ip_of_vera:49451/data_request?id=lu_lua&DeviceNum=8<br />
<br />
Here is a list of the variables that are passed to your Lua code, and also a list of what return values the Lua code should send back. The tag names are explained here: [[Luup_Plugins_ByHand#The_Luup_XML_implementation_file]]:<br />
<br />
==<run>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. <br />
<br />
return value: true or false where true means the function ran ok, false means it failed. <br />
<br />
Sample, including the function/end lines which the Luup engine adds automatically:<br><br />
<br />
<source lang="lua">function SetTarget_run(lul_device, lul_settings)<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue))<br />
return false<br />
end</source><br />
<br />
==<job>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. lul_job is the id number of the job. <br />
<br />
return value: return 2 values with the syntax return a,b. The first is the job status and is a number from 0-5, and the second is the timeout in seconds.<br />
<br />
For more detail see:<br />
*[[Luup_Lua_extensions#Module:_luup.job|Luup Lua extensions: Module luup.job]]<br />
*[[Luup_Plugins_ByHand#.3Crun.2Fjob.2Fincoming.2Ftimeout.3E|Luup Plugins ByHand: <run/job/incoming/timeout>]]<br />
*[[UI_Notes#Job_status|UI Notes: Job status]]<br />
<br />
Sample, including the function/end lines which the Luup engine adds automatically:<br><br />
<br />
<source lang="lua">function SendProntoCode_job(lul_device, lul_settings, lul_job)<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job)<br />
-- 5 = job_WaitingForCallback<br />
-- and we'll wait 10 seconds for incoming data<br />
return 5, 10 <br />
end</source><br />
<br />
==<incoming> (returned by a job) ==<br />
<br />
variables: same as for job above, plus lul_data which is a binary string with the data received <br />
<br />
return value: return 3 values with the syntax return a,b,c. The first two are the same as with job, and the 3rd is a true or false indicating if the incoming data was intended for this job. See [[Luup Plugins ByHand#run.2Fjob.2Fincoming.2Ftimeout_this]] for details. <br />
<br />
Sample, including the function/end lines which the Luup engine adds automatically:<br><br />
<br />
<source lang="lua">function SendProntoCode_incoming(lul_device, lul_settings, lul_job, lul_data)<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job .. " received data: " .. lul_data)<br />
-- 4 = jobDone<br />
-- nil = n/a on the timeout since the job is done<br />
-- true = the incoming data was for us<br />
return 4, nil, true<br />
end</source><br />
<br />
==<timeout>==<br />
<br />
variables: same as for job above.<br />
<br />
return value: same as for job above<br />
<br />
==<incoming> (general, not for a job)==<br />
<br />
variables: lul_device is a number that is the device id. lul_data is a binary string with the data received<br />
<br />
return values: none<br />
<br />
==<scene>==<br />
<br />
variables: none<br />
<br />
return values: boolean (true or false)<br />
<br />
This is how the Lua code in a scene is called. The code is ran before the commands in the scene and if the code returns false, the scene is aborted and the command don't run. If anything else is returned, including true or nothing at all, the scene's commands run as normal.<br />
<br />
==<event>==<br />
<br />
variables: lul_value <br />
<br />
return values: boolean (true or false) <br />
<br />
This is how the Lua code in an event is called. The code is run before the event is processed and the commands in the scene are executed. If the code returns false, the event is aborted and the command(s) in the scene&nbsp;will not&nbsp;run. If anything else is returned, including true or nothing at all, the scene's commands run as normal. lul_value is the new value that is being assigned to the variable which the event is monitoring.<br />
<br />
==<request>==<br />
<br />
variables: lul_request,lul_parameters,lul_outputformat<br />
<br />
return values: lul_data,lul_outputformat<br />
<br />
When you call lu_RegisterHandler (see [[Luup_Lua_extensions]]) to handle a request on a URL, you pass in a function name that will handle the request. This function is called with lul_request as a string containing the request ID, lul_parameters is a table with all the arguments on the URL, and lul_outputformat is the requested format of the data passed on the &output_format= in the URL.<br />
<br />
You return lul_data which is a string containing the response which is forwarded to the client, and optionally lul_outputformat, which is the document type put in the HTML response.<br />
<br />
==<timed> (callback)==<br />
<br />
variables: lul_data<br />
<br />
return values: none<br />
<br />
When you pass a Lua function to be called at a later time with lu_CallFunctionDelay or lu_CallFunctionTimer (see [[Luup_Lua_extensions]]), the function is called with the data (a string) you past.<br />
<br />
==<watch> (callback)==<br />
<br />
variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new<br />
<br />
return values: none<br />
<br />
When you watch a variable with lu_WatchVariable (see [[Luup_Lua_extensions]]), and the variable changes, your callback is called with the information on the variable that changed.<br />
<br />
==<startup>==<br />
<br />
variables: lul_device<br />
<br />
return values: return 3 variables with the syntax return a,b,c where the first is true if the startup was successful or false if not, followed by 2 strings for the comments and the name of the module<br />
<br />
If this function is called in the startup sequence specified in the 'startup' XML tag, return true if the startup was ok, false if it wasn't, followed by some comments and the name of the module, like this: return false,'Cannot get state','gc100' or return true,'ok','gc100'<br />
<br />
==<ir>==<br />
<br />
A pronto code is placed between the <ir> tags. Executing the action will transmit the ProntoCode via the associated IR device. Refer to [[Luup_IR]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_DeclarationsLuup Declarations2014-02-02T21:54:13Z<p>A-lurker: </p>
<hr />
<div>[[Category:Development]]<br />
When the Luup engine calls your Lua code it will pass your code some variables relevant to whatever the code is supposed to do. You can see how your code looks with the full function declarations by bringing up this page in a web browser: http://ip_of_vera:49451/data_request?id=lu_lua&DeviceNum=8<br />
<br />
Here is a list of the variables that are passed to your Lua code, and also a list of what return values the Lua code should send back. The tag names are explained here: [[Luup_Plugins_ByHand#The_Luup_XML_implementation_file]]:<br />
<br />
==<run>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. <br />
<br />
return value: true or false where true means the function ran ok, false means it failed. <br />
<br />
Sample, including the function/end lines which the Luup engine adds automatically:<br><br />
<br />
<source lang="lua">function SetTarget_run(lul_device, lul_settings)<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue))<br />
return false<br />
end</source><br />
<br />
==<job>==<br />
<br />
variables: lul_device is a number that is the device id. lul_settings is a table with all the arguments to the action. lul_job is the id number of the job. <br />
<br />
return value: return 2 values with the syntax return a,b. The first is the job status and is a number from 0-5, and the second is the timeout in seconds.<br />
<br />
For more detail see:<br />
*[[Luup_Lua_extensions#Module:_luup.job|Luup Lua extensions: Module luup.job]]<br />
*[[Luup_Plugins_ByHand#.3Crun.2Fjob.2Fincoming.2Ftimeout.3E|Luup Plugins ByHand: <run/job/incoming/timeout>]]<br />
*[[UI_Notes#Job_status|UI Notes: Job status]]<br />
<br />
Sample, including the function/end lines which the Luup engine adds automatically:<br><br />
<br />
<source lang="lua">function SendProntoCode_job(lul_device, lul_settings, lul_job)<br />
luup.log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job)<br />
-- 5 = job_WaitingForCallback<br />
-- and we'll wait 10 seconds for incoming data<br />
return 5, 10 <br />
end</source><br />
<br />
==<incoming> (returned by a job) ==<br />
<br />
variables: same as for job above, plus lul_data which is a binary string with the data received <br />
<br />
return value: return 3 values with the syntax return a,b,c. The first two are the same as with job, and the 3rd is a true or false indicating if the incoming data was intended for this job. See [[Luup Plugins ByHand#run.2Fjob.2Fincoming.2Ftimeout_this]] for details. <br />
<br />
Sample, including the function/end lines which the Luup engine adds automatically:<br><br />
<br />
<source lang="lua">function SendProntoCode_incoming(lul_device, lul_settings, lul_job, lul_data)<br />
lu_log('device: ' .. tostring(lul_device) .. ' value: ' .. tostring(lul_settings.newTargetValue) .. ' job ID#: ' .. lul_job .. " received data: " .. lul_data)<br />
-- 4 = jobDone<br />
-- nil = n/a on the timeout since the job is done<br />
-- true = the incoming data was for us<br />
return 4, nil, true<br />
end</source><br />
<br />
==<timeout>==<br />
<br />
variables: same as for job above.<br />
<br />
return value: same as for job above<br />
<br />
==<incoming> (general, not for a job)==<br />
<br />
variables: lul_device is a number that is the device id. lul_data is a binary string with the data received<br />
<br />
return values: none<br />
<br />
==<scene>==<br />
<br />
variables: none<br />
<br />
return values: boolean (true or false)<br />
<br />
This is how the Lua code in a scene is called. The code is ran before the commands in the scene and if the code returns false, the scene is aborted and the command don't run. If anything else is returned, including true or nothing at all, the scene's commands run as normal.<br />
<br />
==<event>==<br />
<br />
variables: lul_value <br />
<br />
return values: boolean (true or false) <br />
<br />
This is how the Lua code in an event is called. The code is run before the event is processed and the commands in the scene are executed. If the code returns false, the event is aborted and the command(s) in the scene&nbsp;will not&nbsp;run. If anything else is returned, including true or nothing at all, the scene's commands run as normal. lul_value is the new value that is being assigned to the variable which the event is monitoring.<br />
<br />
==<request>==<br />
<br />
variables: lul_request,lul_parameters,lul_outputformat<br />
<br />
return values: lul_data,lul_outputformat<br />
<br />
When you call lu_RegisterHandler (see [[Luup_Lua_extensions]]) to handle a request on a URL, you pass in a function name that will handle the request. This function is called with lul_request as a string containing the request ID, lul_parameters is a table with all the arguments on the URL, and lul_outputformat is the requested format of the data passed on the &output_format= in the URL.<br />
<br />
You return lul_data which is a string containing the response which is forwarded to the client, and optionally lul_outputformat, which is the document type put in the HTML response.<br />
<br />
==<timed> (callback)==<br />
<br />
variables: lul_data<br />
<br />
return values: none<br />
<br />
When you pass a Lua function to be called at a later time with lu_CallFunctionDelay or lu_CallFunctionTimer (see [[Luup_Lua_extensions]]), the function is called with the data (a string) you past.<br />
<br />
==<watch> (callback)==<br />
<br />
variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new<br />
<br />
return values: none<br />
<br />
When you watch a variable with lu_WatchVariable (see [[Luup_Lua_extensions]]), and the variable changes, your callback is called with the information on the variable that changed.<br />
<br />
==<startup>==<br />
<br />
variables: lul_device<br />
<br />
return values: return 3 variables with the syntax return a,b,c where the first is true if the startup was successful or false if not, followed by 2 strings for the comments and the name of the module<br />
<br />
If this function is called in the startup sequence specified in the 'startup' XML tag, return true if the startup was ok, false if it wasn't, followed by some comments and the name of the module, like this: return false,'Cannot get state','gc100' or return true,'ok','gc100'<br />
<br />
==<ir>==<br />
<br />
A pronto code is placed between the <ir> tags. Executing the action will transmit the ProntoCode via the associated IR device. Refer to [[Luup_IR]]</div>A-lurkerhttp://wiki.mios.com/index.php/Plugin_Creation_TutorialPlugin Creation Tutorial2013-07-18T23:24:44Z<p>A-lurker: </p>
<hr />
<div>[[Category:Development]] [[Category:Plugins]]<br />
=Plugin structure=<br />
A Luup plugin is composed of several types of files that can be broken into two groupings - some of the files are optional:<br />
<br />
<br />
D_GenericPlugin1.xml<br />
<br />
S_GenericPlugin1.xml<br />
<br />
I_GenericPlugin1.xml<br />
<br />
L_GenericPlugin1.xml<br />
<br />
<br />
D_GenericPlugin1.json<br />
<br />
J_GenericPlugin1.xml<br />
<br />
==Files to manage the creation and execution of code in Vera==<br />
*'''D_GenericPlugin1.xml''': one device description file<br />
:The Description file is the prime anchor. All the other files are eventually reached by following the links in the chain or to be more precise; from XML <tag> to XML <tag>. The Description file contains the top level information about the plugin and then links to the Service file(s) as described below:<br />
*'''S_GenericPlugin1.xml''': zero or more service files<br />
:There are many Service files supplied with Vera, which contain the service descriptions for common requirements, such as on/off switches. This allows for consistency among devices. Refer to the [[Luup_UPNP_Files#Device_Files|list of service files provided]] with more [[Luup_Devices|detail here.]] And even [[Luup_UPnP_Variables_and_Actions|more detail here]]. However in many cases the services you require may not exist. In which case you describe them in your own Service file(s) and then implement them in your Implementation file.<br />
*'''I_GenericPlugin1.xml''': one device implementation file<br />
:Refer to [[Luup_Plugins_ByHand|Luup Plugins By Hand]] and [[Luup_Declarations|Luup Declarations]] The Implementation file contains the Actions/Lua code required to implement the services specified in the Service file(s). Additional Lua functions have been made available by the Luup engine to help you program your Vera device: [[Luup_Lua_extensions|Luup Lua extensions]]. Note carefully - this is an XML file, so make sure you escape the XML entities in your Lua code, that need to be escaped.<br />
*'''L_GenericPlugin1.xml''': zero or more Lua files:<br />
:Typically your Lua code is placed in the Implementation file. However for large programs this can make testing a little difficult and the program hard to read or unwieldy. Instead, you can place all your code in the Lua File and just make calls to it from the Implementation file. The use of this file is therefore optional. Those interested in communicating with other internet sites should look at Luasocket. Just Google --> Luasocket reference diego<br />
==Files to help manage User Interface (UIx) executed in the web browser==<br />
*'''D_GenericPlugin1.json''': zero or one device interface file <br />
:Refer to [[Luup_plugins:_Static_JSON_file|Luup plugin: JSON]] and [[Luup_plugin_tabs|Luup plugin: tabs]] You only need this file if you want to control the device directly in the User Interface. If it is not present you can still control the device by invoking its services through scenes, Lua code, URL calls and the like. This file also contains links to the [[Luup_plugin_icons|Icons]] to be used by the device<br />
*'''J_GenericPlugin1.xml''': zero or more JavaScript files<br />
:Called by device JSON file above. Only required when you need the User Interface to do more unusual stuff, rather say just displaying labels, buttons and sliders. The use of this file just depends on how complex the interface to your device is. It's often not needed.<br />
=Examples=<br />
*Google --> MIOS code repository<br />
*[[Luup_Somfy_Walkthrough|Luup Somfy Walkthrough]]<br />
*[[Luup_plugins_and_Luup_code|Luup plugins and Luup code]]<br />
* Vera uses JSON as the native format, so any calls to Vera should request JSON in preference to XML. It's quicker.<br />
=Communicating with your plugin=<br />
[[UI_Notes|UI Notes]] has a good summary on communicating with Vera and/or Vera plugins<br />
*you can access your plugin via Vera scenes and also through the User Interface assuming a D_GenericPlugin1.json is part of your plugin<br />
*you can also use [[Luup_Requests|Luup Requests]]. In particular, have a look the web page at VERAs_IP_address:3480/data_request?id=lu_invoke<br />
*via the MIOS servers - have a look at [[UI_Simple|UI Simple]]<br />
=Caution=<br />
*Odd behavior has been observed when plugin files that are prepended with a Byte Order Mark (BOM) have been uploaded to Vera. Usage of a text editor, such as NotePad++, can be used to ensure a text file is created that does not prepend the BOM. This is in contrast to Windows Notepad that does add in the BOM resulting in trouble for the unsuspecting programmer.<br />
*As mentioned above - all XML files should be escaped properly. This is particularly relevant for any Implementation files containing Lua code, which often contains these characters that need to be escaped: & < > " '<br />
<br />
*You can test your code in UI5-->Apps-->Develop Apps-->Test Luup code (Lua). While '''testing''', always end you code with a "return true". Otherwise you will receive the message "Code failed" on execution, even when your actual code has executed correctly.<br />
*Depending on the Plugin functionality, multiple reloads of the Luup Engine (Reload button) and refreshes of your browser (F5) may be required to fully install the Plugin. For example the WeMo Plugin requires this to be done five times as the user adds in input along the way.</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Lua_extensionsLuup Lua extensions2013-07-13T03:02:25Z<p>A-lurker: </p>
<hr />
<div>In addition to the [[http://lua.org Lua]] commands described in the [[http://www.lua.org/manual/5.1/ Lua reference manual]], you can also reference in your Lua code variables and functions from modules which the Luup engine provides as follows: <br />
<br />
== Module: luup ==<br />
<br />
These are general purpose functions and variables. Call them by using the luup. module, such as:<br> <br />
<br />
<source lang="lua">luup.log('Now running version: ' .. luup.version)</source> <br />
<br />
=== variable: device ===<br />
<br />
The ID of this device instance, if it's running as part of a device <br />
<br />
=== variable: version, version_branch, version_major, version_minor ===<br />
<br />
''version'' contains the version of the luup engine, such as "1.0.843", as a string. The version consists of 3 numbers separated by a period: the branch, the major version, and the minor version. To make it easier to compare against a minimum acceptable version, ''version_branch, version_major and version_minor'' return each component as a number. You can put a comparison in the startup function in your Luup plugin and return false if the minimum version isn't met. The user will then see that the Luup device's startup check failed:<br> <br />
<br />
<source lang="lua">if( version_branch ~= 1 or version_major ~= 0 or version_minor < 843 ) then<br />
luup.log("I need version 1.0.843 minimum to run")<br />
return false<br />
end</source> <br />
<br />
=== variable: longitude ===<br />
<br />
Contains the longitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: latitude ===<br />
<br />
Contains the latitude as a number, as found on the location tab in the setup UI. <br />
<br />
=== variable: timezone ===<br />
<br />
Contains the timezone as a number of hours offset from UTC, as found on the location tab in the setup UI. It accounts for DST, so, for example, Pacific Standard time will be -8 or -9 depending on DST.<br />
<br />
'''Note:''' Contains 0 for MiOS < 1.5.250 (Vera V2) / < 1.5.249 (Vera V3).<br />
<br />
=== variable: city ===<br />
<br />
Contains the city as a string, as found on the location tab in the setup UI. <br />
<br />
=== variable: devices ===<br />
<br />
Contains all the devices in the system as a table indexed by the device number.<br />
<br />
The members are:<br />
* '''room_num''': (number) This is the number of the room the device is in. <br />
* '''device_type''': (string) This is a string representing the type of the device.<br />
* '''category_num''': (number) This is a category for the device. See: [[Luup_Device_Categories]] for a list. <br />
* '''subcategory_num''': (number) This is a sub category for the device.<br />
* '''device_num_parent''': (number) This is the number of the parent device. See: [[Lua Device Structure]] for details. <br />
* '''ip''': (string) If this device is IP based, this is the IP address. <br />
* '''mac''': (string) If this device is IP based, this is the MAC address. <br />
* '''user''': (string) If this device is IP based and requires http authentication, this is the username<br />
* '''pass''': (string) If this device is IP based and requires http authentication, this is the password<br />
* '''id''': (string) If this device has an internal ID that is specific to the device, it is contained here. For example, for Z-Wave devices this is the Node ID, and for Insteon device it is the Insteon ID. <br />
* '''embedded''': (boolean) If this device is embedded, it means that it doesn't have its own room or exist as a separate device. It should be considered part of its parent. Like a 3-in-1 sensor is a device with 3 embedded child devices. <br />
* '''hidden''': (boolean) If true the user checked the 'hidden' box and doesn't want to see the device on the dashboard. <br />
* '''invisible''': (boolean) If true the device is 'for internal use only' and shouldn't be presented to the user. <br />
* '''description''': (string) This is the text description for the device as supplied by the user in the web UI. <br />
* '''udn''': (string) This is the UDN for the UPnP device.<br />
<br />
<br />
Example to log device #5's IP address and its internal ID:<br />
<source lang="lua">luup.log('Device #5 ip: ' .. luup.devices[5].ip .. ' id: ' .. luup.devices[5].id)</source> <br />
<br />
<br />
This code will log all the attributes from all the devices:<br />
<source lang="lua">for k, v in pairs(luup.devices) do<br />
for k2, v2 in pairs(v) do<br />
luup.log("Device #" .. k .. ":" .. k2 .. "=" .. tostring(v2))<br />
end<br />
end<br />
<br />
return true</source><br />
<br />
=== variable: rooms ===<br />
<br />
Contains all the rooms as a table of strings indexed by the room number. Example:<br> <br />
<br />
<source lang="lua">luup.log('Room #1 is called: ' .. luup.rooms[1])</source> <br />
<br />
=== variable: scenes ===<br />
<br />
Contains all the scenes in the system as a table indexed by the scene number. The members are: room_num (number), description(string), hidden(boolean)<br />
<br />
=== variable: remotes ===<br />
<br />
Contains all the remotes in the system as a table indexed by the remote id. The members are: remote_file (string), room_num (number), description(string) <br />
<br />
=== variable: event_server ===<br />
<br />
type: string<br />
<br />
Contains the notification/event server. On UI5 it can be either ''cms1.mios.com'' or ''cms2.mios.com''.<br />
<br />
=== variable: ra_server ===<br />
<br />
type: string<br />
<br />
Contains the remote access server. Can be either ''fwd1.mios.com'' or ''fwd2.mios.com''.<br />
<br />
=== variable: pk_accesspoint ===<br />
<br />
type: number<br />
<br />
Contains the serial number of this Vera.<br />
<br />
=== variable: hw_key ===<br />
<br />
type: string<br />
<br />
Contains the Vera hardware key.<br />
<br />
=== function: log ===<br />
<br />
parameters: what_to_log (string), log_level (optional, number) <br />
<br />
return: nothing <br />
<br />
Writes what_to_log to the log, /var/log/cmh/LuaUPnP.log, with the given log_level, which is 50 by default. See: [[Luup Loglevels]] <br />
<br />
=== function: task ===<br />
<br />
parameters: message (string), status (number), description (string), handle (number) <br />
<br />
return: handle (number) <br />
<br />
When the Luup engine is starting status messages are displayed for the various modules as they're initialized. Normally each device, including Luup devices, automatically log their status and the user is shown an error if the device doesn't start, such as if the 'startup' function returns an error. <br />
<br />
If you have other startup sequences which you want the user to see to know that startup hasn't finished yet, call this function passing in a handle of -1 for the first call. The status should be: 1=Busy, 2=Error, 4=Successful. Message is the current state, such as 'downloading', and description describes the module, like 'Smartphone UI'. After the first call, store the handle and pass it on future calls to update the status rather than add a new one. <br />
<br />
=== function: call_delay ===<br />
<br />
parameters: function_name (string), seconds (number), data (string), thread (bool) <br />
<br />
returns: result (number) <br />
<br />
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. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. function_name will only be called once and you must call call_delay again if you want it called again, or use call_timer instead. <br />
<br />
If thread is specified and is true or 1, the call back will be made in it's own thread and can block if needed. Normally it is called by a worker thread and is expected to return immediately.<br />
<br />
As of December 19, 2011, for all builds after 1.5.237, the 'thread' will be ignored. Each Lua state has its own worker thread now, so all calls to call_delay and call_timer will occur in a separate thread.<br />
<br />
=== function: call_timer ===<br />
<br />
parameters: function_name (string), type (number), time (string), days (string), data (string) <br />
<br />
returns: result (number) <br />
<br />
The function 'function_name', which must be passed as a string, will be called when the timer is triggered, and will be passed the string 'data'. The function returns 0 if successful. See: [[Luup Declarations#timed_function_callback]] for the names and syntax of the parameters that will be passed to function_name. <br />
<br />
Type is 1=Interval timer, 2=Day of week timer, 3=Day of month timer, 4=Absolute timer. For an interval timer, days is not used, and Time should be a number of seconds, minutes, or hours using an optional 'h' or 'm' suffix. Example: 30=call in 30 seconds, 5m=call in 5 minutes, 2h=call in 2 hours. For a day of week timer, Days is a comma separated list with the days of the week where 1=Monday and 7=Sunday. Time is the time of day in hh:mm:ss format. Time can also include an 'r' at the end for Sunrise or a 't' for Sunset and the time is relative to sunrise/sunset. For example: Days="3,5" Time="20:30:00" means your function will be called on the next Wed or Fri at 8:30pm. Days="1,7" Time="-3:00:00r" means your function will be called on the next Monday or Sunday 3 hours before sunrise. Day of month works the same way except Days is a comma separated list of days of the month, such as "15,20,30". For an absolute timer, Days is not used, and Time should be in the format: "yyyy-mm-dd hh:mm:ss" <br />
<br />
Data can be a string passed back to the function. The function should be declared so it takes a single argument, which is this data.<br> <br />
<br />
<source lang="lua">function refreshCache(stuff)<br />
....<br />
end<br />
<br />
function startup()<br />
--<br />
-- Setup an interval-based timer to call refreshCache after 30 minutes.<br />
-- Note that if you want it to "recur" then you need to call this function again<br />
-- at the end of the refreshCache() implementation.<br />
--<br />
luup.call_timer("refreshCache", 1, "30m", "", "SomeStuff")<br />
end</source> <br />
<br />
=== function: is_ready ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ready (boolean) <br />
<br />
version: UI5 and above<br />
<br />
Checks whether a device has successfully completed it's startup sequence. If so, <tt>is_ready</tt> returns <tt>true</tt>. If your device shouldn't process incoming data until the startup sequence is finished, you may want to add a condition to the <tt><incoming> block</tt> that only processes data if <tt>is_ready(lul_device)</tt> is <tt>true</tt>.<br />
<br />
The <tt>device</tt> parameter, if it's a string, is interpreted as a udn. If it's a number, it's interpreted as a device number.<br />
<br />
<source lang="lua"><incoming><br />
if (luup.is_ready(lul_device) == false) then<br />
return<br />
end<br />
<br />
doSomething(lul_device)<br />
</incoming></source><br />
<br />
=== function: call_action ===<br />
<br />
parameters: service (string), action (string), arguments (table), device (string or number) <br />
<br />
returns: error (number), error_msg (string), job (number), arguments (table) <br />
<br />
Invokes the UPnP service + action, passing in the arguments (table of string-&gt;string pairs) to the device, which, if it's a string, is interpreted as a udn, and if it's a number, is the device number. If the invocation could not be made, only error will be returned with a value of -1. Otherwise, all 4 values are returned. error is 0 if the UPnP device reported the action was successful. arguments is a table of string-&gt;string pairs with the return arguments from the action. If the action is handled asynchronously by a Luup job, then the job number will be returned as a positive integer. <br />
<br />
Example to dim device #5 to 50%:<br> <br />
<br />
<source lang="lua">local lul_arguments = {}<br />
lul_arguments["newLoadlevelTarget"] = 50<br />
lul_resultcode, lul_resultstring, lul_job, lul_returnarguments = luup.call_action("urn:upnp-org:serviceId:Dimming1",<br />
"SetLoadLevelTarget", lul_arguments,<br />
5)</source><br />
<br />
=== function: variable_set ===<br />
<br />
parameters: service (string), variable (string), value (string), device (string or number), [startup (bool)] <br />
<br />
returns: nothing <br />
<br />
The UPnP service+variable will be set to value for device, which if it's a string, is interpreted as a udn, and if it's a number, as a device id. If there are events or notifications tied to the variable they will be fired. <br />
<br />
Optionally, you can add an argument 'startup'. If startup is true, this change will be considered a startup value, and if the variable is set to it's existing value, events and notifications will ''not'' be fired. <br />
<br />
=== function: variable_get ===<br />
<br />
parameters: service (string), variable (string), device (string or number) <br />
<br />
returns: value (string) and time (number) or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Returns the value of the UPnP service+variable and the time when the variable was last modified, as a unix time stamp (number of seconds since 1/1/1970). If the service+variable or device does not exist, it returns nothing. You can assign just the value to a variable, as follows:<br> <br />
<br />
<source lang="lua">local value = luup.variable_get("urn:upnp-org:serviceId:Dimming1", "LoadLevelTarget", 5)<br />
luup.log("Dim level for device #5 is: " .. value)</source><br />
<br />
=== function: attr_set ===<br />
<br />
parameters: attribute (string), value(string), device (string or number) <br />
<br />
returns: none <br />
<br />
Sets the top level attribute for the device to value. Examples of attributes are 'mac', 'name', 'id', etc.<br />
<br />
=== function: attr_get ===<br />
<br />
parameters: attribute (string), device (string or number) <br />
<br />
returns: string or none (note: none means nothing at all. It does not mean 'nil')<br />
<br />
Gets the top level attribute for the device. Examples of attributes are 'mac', 'name', 'id', etc. If the attribute doesn't exist, it returns nothing. If nothing is passed in for device, it gets the top level attribute from the master userdata, like firmware_version.<br />
<br />
<source lang="lua">-- this code logs nil if theDeviceNumber is invalid<br />
local theName = luup.attr_get ('name', theDeviceNumber)<br />
luup.log(theName)<br />
return true<br />
</source><br />
<br />
<br />
<source lang="lua">-- this code fails if theDeviceNumber is invalid<br />
luup.log(luup.attr_get ('name', theDeviceNumber))<br />
return true<br />
</source><br />
<br />
=== function: register_handler ===<br />
<br />
parameters: function_name (string), request_name (string) <br />
<br />
returns: nothing <br />
<br />
When a certain URL is requested from a web browser or other HTTP get, function_name will be called and whatever string it returns will be returned. <br />
<br />
See the WAP mobile phone plugin as an example:<br> <br />
<br />
<source lang="lua">luup.register_handler("lug_WapRequest","wap")<br />
<br />
function lug_WapRequest (lul_request, lul_parameters, lul_outputformat)<br />
local lul_html = "<head>\n" ..<br />
"<title>Main</title>\n" ..<br />
"</head>\n" ..<br />
"<body>\n" ..<br />
"Choose a room:<br/>\n"<br />
return lul_html<br />
end</source> <br />
<br />
The request is made with the URL: data_request?id=lr_[the registered name] on port 49451. So: http://192.168.1.1:49451/data_request?id=lr_wap will return the web page defined in the function lug_WapRequest in the example above. <br />
<br />
=== function: variable_watch ===<br />
<br />
parameters: function_name (string), service (string), variable (string or nil), device (string or number) <br />
<br />
returns: nothing <br />
<br />
Whenever the UPnP variable is changed for the specified device, which if a string is interpreted as a UDN and if a number as a device ID, ''function_name'' will be called. See [[Luup Declarations#watch_callback]] for the values that will be passed to ''function_name''. If variable is nil, ''function_name'' will be called whenever any variable in the service is changed. <br />
<br />
<br><br />
<br />
=== function: devices_by_service ===<br />
<br />
parameters: <br />
<br />
returns: <br />
<br />
=== function: device_supports_service ===<br />
<br />
parameters: service ID (string), device (string or number)<br />
<br />
returns: ''true'' if the device supports the service, ''false'' otherwise<br />
<br />
A device supports a service if there is at least a command or state variable defined for that device using that service. Setting UPnP variables is unrestricted and free form, and the engine doesn't really know if a device actually uses it or does anything with it. So this function isn't really definitive.<br />
<br />
=== function: set_failure ===<br />
<br />
parameters: value (boolean), device (string or number) <br />
<br />
returns: <br />
<br />
Luup maintains a 'failure' flag for every device to indicate if it is not functioning. You can set the flag to true if the device is failing. If device is a string it is interpreted as a udn, if it's a number, as a device id. The lu_status URL will show for the device: <tooltip display="1" tag2="Lua Failure"/> and Lua Failure is shown in red in UI5 for the device.<br />
<br />
=== function: is_night ===<br />
<br />
parameters: none <br />
<br />
returns: ''true'' if it's past sunset and before sunrise, ''false'' otherwise.<br />
<br />
=== function: sleep ===<br />
<br />
parameters: number of milliseconds <br />
<br />
returns: none <br />
<br />
Sleeps a certain number of milliseconds<br />
<br />
=== function: sunset / sunrise ===<br />
<br />
parameters: none<br />
<br />
returns: The next sunset / sunrise in a Unix timestamp (i.e. the number of seconds since 1/1/1970 in UTC time). You can do a diff with <tt>os.time</tt> to see how long it will be for the next event. <tt>luup.sunset-os.time</tt> is the number of seconds before the next sunset. Be sure the location and timezone are properly set or the sunset/sunrise will be wrong.<br />
<br />
required firmware: 1.5.353<br />
<br />
== Module: luup.inet ==<br />
<br />
=== function: wget ===<br />
<br />
parameters: URL (String), Timeout (Number), Username (String), Password (String) <br />
<br />
returns httpStatusCode (Number), content (String) <br />
<br />
This reads the URL and returns 2 variables: the first is a numeric error code which is 0 if successful, and the second is a string containing the contents of the page. If '''Timeout''' is specified, the function will timeout after that many seconds. The default value for '''Timeout''' is 5 seconds. If '''Username''' and '''Password''' are specified, they will be used for HTTP Basic Authentication. <br />
<br />
<br><br />
<br />
== Module: luup.chdev ==<br />
<br />
Contains functions for a parent to synchronize its child devices. Whenever a device has multiple end-points, the devices are represented in a parent/child fashion where the parent device is responsible for reporting what child devices it has and giving each one a unique id. For example in the sample [[Luup Somfy Walkthrough]] there is a parent device, which is the interface module that controls up to 16 blinds, and up to 16 child devices, one for each blind. As shown in that sample, the parent calls start, then enumerates each child device with append, and finally calls sync. You will need to pass the same value for device to append and sync that you passed to start. <br />
<br />
=== function: start ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: ptr (binary object) <br />
<br />
Tells Luup you will start enumerating the children of device. If device is a string it is interpreted as a udn, if it's a number, as a device id. The return value is a binary object which you cannot do anything with in Lua, but you do pass it to the append and sync functions. <br />
<br />
=== function: append ===<br />
<br />
parameters: device (string or number), ptr (binary object), id (string), description (string), device_type (string), device_filename (string), implementation_filename (string), parameters (string), embedded (boolean) [, invisible (boolean)]<br />
<br />
returns: nothing <br />
<br />
Adds one child to <tt>device</tt>. If <tt>device</tt> is a string it is interpreted as a udn, if it's a number, as a device id. <br />
<br />
Pass in the <tt>ptr</tt> which you received from the <tt>luup.chdev.start</tt> call. Give each child a unique id so you can keep track of which is which. You can optionally provide a <tt>description</tt> which the user sees in the user interface.<br />
<br />
<tt>device_type</tt> is the UPnP device type, such as <tt>urn:schemas-upnp-org:device:BinaryLight:1</tt>.<br />
<br />
If <tt>device_filename</tt> is specified, that is the name of the&nbsp;XML file with the UPnP device specification. The deviceType from the filename will override any <tt>device_type</tt> you set manually. If the device_file contains the implementation file for this child device you do not need to specify it in <tt>implementation_filename</tt>. Otherwise, if there is a Luup implementation for this child device and it's not being handled by the parent device, you can specify it in <tt>implementation_filename</tt>.<br />
<br />
If <tt>embedded</tt> is true, the 'embedded' flag is set for the device which generally means that the parent and all the children will be displayed as one compound device, or group, rather than as separate devices which you can put in their own rooms. <br />
<br />
The <tt>parameters</tt> are UPnP service,variables you want set when the device is created. You can specify multiple variables by separating them with a line feed (\n) and use a <tt>,</tt> and <tt>=</tt> to separate service, variable and value, like this: <tt>service,variable=value\nservice</tt>...<br />
<br />
<source lang="lua"><br />
luup.chdev.append(device, children,<br />
string.format("Input-%d", i), string.format("Input %d", i),<br />
"urn:schemas-micasaverde-com:device:TemperatureSensor:1", "D_TemperatureSensor1.xml",<br />
"", "urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=50", true)<br />
</source><br />
<br />
=== function: sync ===<br />
<br />
parameters: device (string or number), ptr (binary object), <br />
<br />
returns: nothing <br />
<br />
If device is a string it is interpreted as a udn, if it's a number, as a device id. Pass in the ptr which you received from the start function. Tells the Luup engine you have finished enumerating the child devices. If the child devices have changed in any way, the new device tree will be written to the configuration file and the Luup engine is reset. <br />
<br />
== Module: io ==<br />
io.open<br/><br />
io.write<br/><br />
io.intercept<br/><br />
io.read<br/><br />
io.is_connected<br />
<br />
=== function: open ===<br />
<br />
parameters: device (string or number), ip (string), port (number), <br />
<br />
returns: nothing <br />
<br />
If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This opens a socket on 'port' to 'ip' and stores the handle to the socket in 'device'. The opening of a socket can take time depending on the network, and a Luup function should return quickly whenever possible because each top-level device's Lua implementation runs in a single thread. So the actual opening of the socket occurs asynchronously and this function returns nothing. You will know that the socket opening failed if your subsequent call to write fails. <br />
<br />
Generally you do not need to call the open function because the socket is usually started automatically when the Luup engine starts. This is because the user typically either (a) associates a device with the destination io device, such as selecting an RS232 port for an alarm panel, where the RS232 is proxied by a socket, or (b) because the configuration settings for the device already include an IP address and port.<br />
<br />
=== function: write ===<br />
<br />
parameters: data (string), device (string or number) <br />
<br />
returns: result (boolean) <br />
<br />
In Lua a string can contain binary data, so data may be a binary block. If 'device' is a string it is interpreted as a UDN; if it's a number, as a device ID. This sends data on the socket that was opened automatically or with the open function above, and associated to 'device'. If the socket is not already open, write will wait up to 5 seconds for the socket before it returns an error. Result is 'true' if the data was sent successfully, and is 'false' or nill if an error occurred. <br />
<br />
=== function: intercept ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: nothing <br />
<br />
Normally when data comes in on a socket (I/O Port), the block of data is first passed to any pending jobs that are running for the device and are marked as 'waiting for data'. If there are none, or if none of the jobs' incoming data handlers report that they consumed (i.e. processed) the data, then the block of data is passed to the general 'incoming' function handler for the device. If you want to bypass this normal mechanism and read data directly from the socket, call intercept first to tell Luup you want to read incoming data with the read function. This is generally used during the initialization or startup sequences for devices. For example, you may need to send some data (a), receive some response (b), send some more data (c), receive another response (d), etc. In this case you would call 'intercept' first, then send a, then call read and confirm you got b, then call intercept again, then send c, then read d, and so on. <br />
<br />
You can call the read function without calling intercept and any incoming data will be returned by that function after it's called. The reason why you generally must call intercept is because normally you want to send some data and get a response. If you write the code like this ''send(data) data=read()'' then it's possible the response will arrive in the brief moment between the execution of send() and read(), and therefore get sent to the incoming data handler for the device. Intercept tells Luup to buffer any incoming data until the next read, bypassing the normal incoming data handler. So ''intercept() send(data) data=read()'' ensures that read will always get the response. If the device you're communicating with sends unsolicited data then there's the risk that the data you read is not the response you're looking for. If so, you can manually pass the response packet to the incoming data handler. <br />
<br />
**TBD: Add a function to do this**<br />
<br />
=== function: read ===<br />
<br />
parameters: timeout (number), device (string or number) <br />
<br />
returns: data (string) <br />
<br />
This reads a block of data from the socket. You must have called ''intercept'' previously so the data is passed. The time unit for ''timeout'' is seconds.<br><br />
<br />
=== function: is_connected ===<br />
<br />
parameters: device (string or number) <br />
<br />
returns: connected (boolean) <br />
<br />
This function returns true if there is a valid IO port connected, otherwise returns false<br />
<br />
== Module: luup.job ==<br />
<br />
=== function: status ===<br />
<br />
parameters: job_number (number), device (string or number) <br />
<br />
returns: job_status (number), notes (string) <br />
<br />
If '''job_number''' is invalid the function returns ''-1''. If '''device''' is a string it is interpreted as an UDN, if it's a number, as a device ID.<br />
<br />
This is the list with all job statuses and their meaning:<br />
* '''-1''': No job, i.e. job doesn't exist.<br />
* '''0''': Job waiting to start.<br />
* '''1''': Job in progress.<br />
* '''2''': Job error.<br />
* '''3''': Job aborted.<br />
* '''4''': Job done.<br />
* '''5''': Job waiting for callback. Used in special cases.<br />
* '''6''': Job requeue. If the job was aborted and needs to be started, use this special value.<br />
* '''7''': Job in progress with pending data. This means the job is waiting for data, but can't take it now.<br />
<br />
=== function: set ===<br />
<br />
parameters: job (userdata), setting (string), value (string) <br />
<br />
returns: nothing <br />
<br />
This stores a setting for a job. <br />
<br />
<source lang="lua"><job><br />
luup.job.set(lul_job, "comments", "In progress...")<br />
local comments = luup.job.setting(lul_job, "comments")<br />
luup.log("job comments = " .. comments)<br />
</job></source><br />
<br />
=== function: setting ===<br />
<br />
parameters: job (userdata), setting (string) <br />
<br />
returns: value (string) <br />
<br />
This returns a setting for a job. <br />
<br />
[[Category:Development]]</div>A-lurkerhttp://wiki.mios.com/index.php/Luup_Plugins_ByHandLuup Plugins ByHand2013-07-12T08:52:47Z<p>A-lurker: </p>
<hr />
<div>[[Category:Development]]<br />
==Description of the XML files==<br />
<br />
Because the web generator is not yet operational (as of June 2013) you need to create the XML files by hand. The easiest way to do this is to start with an existing device as a template that you modify to suit your needs. In Vera's Setup Web UI, choose Devices, Luup plug-ins (in UI5, choose Apps, Develop Apps, Luup Files). There is a list of all the XML files which came with Vera by default. Files that start with D_ are UPnP device specifications. Files that start with S_ are UPnP service specifications. And files that start with I_ are Luup implementation files. Click view to see the XML file. We recommend using the Firefox web browser because it has built-in support for nicely displaying XML files in a graphical tree. XML can be hard to read in other web browser.<br />
<br />
When you open a device specification file the xml tag deviceType defines what kind of device it is. This is how a UPnP Control Point knows what this device is. If the device type starts with urn:schemas-upnp-org, that means it's a UPnP defined standard, and the list of services the device must support are defined by the UPnP forum. If you're creating your own device type you can substitute the schemas-upnp-org with your own web domain name and change the other parts of the name. But stick to the same convention and use only a-z, 0-9 and hypens (-) and colons (:). Remember though that if you use your own device type chances are UPnP Control Points won't know what to do with it unless the author of the control point makes a custom addition for you.<br />
<br />
The device file references the service files (S_) and gives each service a serviceType and a serviceId. The serviceType what defines the standard UPnP service. But since it's possible to have multiple instances of a given service, each needs a unique serviceId. For example, there is a standard UPnP service to set the setpoint on a thermostat called: urn:schemas-upnp-org:service:TemperatureSetpoint:1. But many thermostats have multiple setpoints, such as heat and cool. So in the standard for a UPnP thermostat device (D_HVAC_ZoneThermostat1.xml), there are 2 instances of the serviceType "urn:schemas-upnp-org:service:TemperatureSetpoint:1", one has the id "urn:upnp-org:serviceId:TemperatureSetpoint1_Cool" and the other "urn:upnp-org:serviceId:TemperatureSetpoint1_Heat". They both use the same service specification, S_TemperatureSetpoint1.xml, which is in the XML tag SCPDURL.<br />
<br />
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.<br />
<br />
The UPnP specification allows that we can add our own custom xml tags. So we add the xml tags "implemenationList" to the device specification which references the implementation files that device will use.<br />
<br />
== The Luup XML implementation file ==<br />
<br />
I_GC100.xml is a full featured, functional implementation for the Global Cache GC100, which is an ethernet device with relay switches, input sensors, infrared transmitters and serial ports. I_GC100.xml shows a complete Luup plugin with parent/child devices. The corresponding UPnP Device specification file is D_GC100.xml. In all the implementation files the top-level XML tag (ie root node) is called "implementation". It contains the following elements: <br />
<br />
=== <settings> ===<br />
<br />
The settings node contains various settings for the implementation. <br />
<br />
=== <protocol> ===<br />
Is the protocol to use to talk to the device if you'll be sending data over the network or a serial port. The protocol tag tells Luup what's considered a single ''chunk'' of data. By using a format, from the supported list below, you avoid byte-by-byte processing on input streams as the Luup engine will ''chunk'' the data to you and pass it to your Lua code handling <tt><incoming></tt> requests.<br />
Lua code is much cleaner when it handles data in chunks. If you have a protocol that's not natively supported, and is likely to be used by other devices, let us know and we'll add it to the Luup engine so you don't need to mess with it.<br />
<br />
Valid values for this tag are:<br />
*<tt>crlf</tt> - all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. <br />
*<tt>stxetx</tt> - all incoming commands are surrounded by STX and ETX characters. If you send the string "test" the framework will add the STX before and the ETX at the end, and if the string "<tt>''<stx>''test''<etx>''</tt>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.<br />
*<tt>raw</tt> - makes no modifications to outgoing data, and calls your incoming data callback for each byte received. This adds more overhead since the engine needs to call your Luup function for every character, and makes your code complex. So, generally avoid using '<tt>raw</tt>' and let us add support for your protocol if you have a new one we don't yet support.<br />
<br />
=== <ioPort> ===<br />
One way to have the device talk to another device on the internet. If you put a TCP port number here, an outgoing connection attempt is made automatically when the device initializes. (The remote IP address should be placed in the 'ip' box in the device's advanced configuration tab.) If you don't use this tag, you can use luup.io.open(..) instead.<br />
<br />
=== <handleChildren> ===<br />
If this flag is 1, then any actions that are sent to one of this device's children will be handled within this devices implementation file. Look at the I_GC100.xml file to see how this is done. The GC100 is a parent device which has no services or actions, but has child devices like IR transmitters and relays. This implementation file handles the SwitchPower/SetTarget action for the relays, as well as the SendProntoCode action for the ir transmitters, even though those actions will be sent to the GC100's child device--not the device GC100 itself.<br />
<br />
=== <functions> ===<br />
Put here the Lua code for functions you want to be able to use in other places in your Lua code. You can also declare local variables here before the functions are described. Watch out for XML syntax. In particular, the > and >= comparison operators should be escaped.<br />
<br />
=== <files> ===<br />
As an alternative to the <functions> element, you can put your Lua implementation in a separate file containing pure Lua code and not worry about XML escapes. By convention, the file name should begin with L_ and end with .lua. as in <files>L_MyDevice.lua</files><br />
<br />
=== <startup> ===<br />
<br />
This tag is a comma separated list of functions you want the Luup engine to call when it's starting up. Generally these are functions in the 'functions' tag. <br />
<br />
=== <actionList> ===<br />
<br />
This is where you specify what to do when an action comes in. Create an "action" node for each action, which contains "serviceId" and "name" tags to indicate what action is being implemented. The "name" tag refers to the name of the Action as defined in the UPnP Service Specification file. <br />
<br />
==== function declarations ====<br />
<br />
Whatever Lua code you create in the tags will be put inside a function automatically by the Luup engine, and your code will be passed variables that are relevant to whatever the code needs to do. For example, the code inside the 'run' tag is passed lul_device,lul_settings where lul_device is the id of the device the action was sent to, and lul_settings has the arguments to the UPnP action. See: [[Luup Declarations]] for details.<br />
<br />
There are several different nodes you can put within the "action" node. <br />
<br />
==== <run/job/incoming/timeout> ====<br />
<br />
Put Lua code in the 'run' node that is run immediately when the action is received. This code should be short and quick and return right away because the UPnP Control Point will probably be blocked while it waits for the reply. Also, your Luup device will not do anything else while the 'run' code is executing. If it will take some time to handle the action, use the 'job' tag instead. Job's will run asynchronously, meaning they happen in the background. If you implement this action in a Job, you won't be able to give the result code (ie success/failure) to the UPnP Control Point because the Luup engine gives the Control Point an "action successful" as soon as it creates the job. You can put code in both the 'run' and 'job' tags. In this case, the code in the 'run' tag is run immediately and can return an error condition which is sent back to the UPnP Control Point, or, if it returns 'OK', then Luup will send the 'ok' to the UPnP Control Point and run the job later. The job can take as long as you want. <br />
<br />
The UPnP forum did not create an action "on" or "off" for a light switch because they know that it can take some time to actually turn the light on or off, and you don't want to block the UPnP control point waiting for the light to go on or off. So, the UPnP action to turn a light on is called "SetTarget". The UPnP action tells the control point it executed the action ok simply when it receives the action. If the control point wants to know for sure if the light actually turned on, the control point should watch the 'Status' variable and see if it changes to "1" when the light is actually on. <br />
<br />
In the case of the GC100, (see: I_GC100.xml), on/off of the relays happens immediately. So the implementation is inside a 'run' tag, which sets the "Status" variables. In the case of SendProntoCode, the implementation is inside a 'job' tag since the pronto commands may take time. <br />
<br />
When you have Lua code inside a 'job' tag, Luup will return 'OK' to the Control Point in response to the action, and then queues up the job and runs the Lua in your 'job' code. The job code returns 2 values:<br />
<br />
1) the status of the job<br />
<br />
2) how long to wait before the job times out in seconds.<br />
<br />
The status can be:<br />
*0=job_WaitingToStart: In vera's UI a job in this state is displayed as a gray icon. It means it's waiting to start. If you return this value your 'job' code will be run again in the 'timeout' seconds <br />
*2=job_Error, or 3=job_Aborted: In vera's UI a job in this state is displayed as a red icon. This means the job failed. Your code won't be run again. <br />
*4=job_Done: In vera's UI a job in this state is displayed as a green icon. This means the job finished ok. Your code won't be run again. <br />
*5=job_WaitingForCallback: In vera's UI a job in this state is displayed as a moving blue icon. This means the job is running and you're waiting for return data. Any data that comes in while the job is in this state will go to the lua code in the job's 'incoming' tag. If no data comes in before the number of seconds you return in the timeout, then the code in the job's 'timeout' tag is run. <br />
<br />
So in the SendProntoCode job code for the I_GC100, we return 5,10 which means we're waiting for data and should wait up to 10 seconds. <br />
<br />
While a job is active, a UPnP control point will see the status of the job when it uses the GetStatus action for Vera. In Vera's web ui an active job is shown as an icon next to the device. While Luup is in the middle of executing the Lua code for your job, the status is reported to the control point as 1=job_InProgress. <br />
<br />
Whenever your job is in status 5=job_WaitingForCallback, whatever data comes in on the I/O port will be first given to the job's 'incoming' Lua code *before* it's given to the general purpose 'incoming' Lua code (see below). If the incoming data is not for this job, you should not return a 'true' as the 3rd return parameter from 'incoming', and the Luup engine will forward the incoming data to the general purpose 'incoming' Lua code. If you do return 'true', the Luup engine assumes you handled the incoming data and there's nothing more to do with it. See the incoming tag for the SendProntoCode job code for the I_GC100 as an example. The 'incoming' code returns 3 values. The first 2 are the same as for the 'job', and the 3rd is true or false depending on if the incoming data was for this job or not. <br />
<br />
The Luup engine will only run one chunk of Lua code at a time. This is necessary to prevent problems with the shared variables. However, there can be multiple jobs in the 5=job_WaitingForCallback state. When one job is run, and it returns the status 5=job_WaitingForCallback, the Luup engine will start another job while the first one is waiting. So when incoming data is received, the Luup engine will pass the data to the 'incoming' Lua code for all the jobs in the 5=job_WaitingForCallback state, in the order the jobs were created. As soon as a job returns 'true' for the 3rd parameter, Luup discards the incoming data. As long as the jobs return 'false' the data is passed on to the next job, and eventually to the general purpose 'incoming' Lua code. If, when you start a job, you do not want other jobs to run until your job is done, call the [[Luup Lua extensions]] lu_block_jobs. Other Luup plugins will continue to run asynchronously, but no other jobs will run for this particular Luup plugin until this job becomes 'done' or 'error/abort'. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <incoming> ====<br />
<br />
This block defines what to do when data is received by the device, if this device is talking on a serial port, ethernet port, or some other generic I/O port. The Luup engine handles low-level I/O for these types of ports so you only need to call one of the [[Luup Lua extensions]] to send data on the port. <br />
<br />
This is the general purpose incoming data handler that the Luup engine will call whenever data comes in on an I/O Port. It has a node 'lua' which contains the Lua code that is called. <br />
<br />
[[Luup Declarations]] lists what variables this Lua code receives and what it should return. <br />
<br />
==== <ir> ====<br />
<br />
Put an ir code in pronto format in this tag. Luup will send a SendProntoCode action to whatever device is specified as the [[Luup IO Device]] in the [[Luup Configuration File]]. <br />
<br />
==Walkthrough to create a device==<br />
<br />
We have documented step-by-step the detailed process of creating a Luup interface, including everything that was done to debug, for Somfy blind control device (see: [http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf]), which is a simple 1-way serial device (ie send data, but don't get any response) here: [[Luup_Somfy_Walkthrough]]<br />
<br />
No matter type of Luup plugin you'll be creating the procedure to create it is the same. This is true for Luup plugins which do not talk to any external hardware, such as a 'Weather' plugin to provide weather services, as well as plugins that talk to infrared devices like a TV, and serial/network devices.<br />
<br />
The first step is to create the UPnP device specification and service specification files. Whenever possible you will want to re-use existing service specification files, as explained in "Introduction to UPnP" [[Luup_Plugins#Introduction_to_UPnP here]]. And if your device is functionally the same as an existing UPnP Device type, like a light switch, you should re-use an existing UPnP Device Specification file also so that a UPnP Control Point will know how to control your device with modification. A list of all currently known UPnP Device and Service specification files is here: [[Luup_UPNP_Files]]. Find the existing UPnP device file that is most similar to the Luup plugin you are creating, and list all the services that device will implement. Download these files on your computer either from the [[Luup_UPNP_Files]] page, or by going to Vera's setup page and choosing Devices, Luup plugins.<br />
<br />
Modify the UPnP device specification file as needed, such as changing the manufacturer and model. Do not worry about the UDN tag as Luup will create a UDN for you automatically. If your device is functionally the same as the template you started from, leave the deviceType the same. If it's different, modify the deviceType replacing the "schemas-upnp-org" or "micasaverde-com" with some domain that you have, or if you don't have one, just use your name. Leave the :device: but change the word after it to describe your device. The deviceType must contain only a-z, 0-9, : and -.<br />
<br />
In the '''<serviceList>''' tag, add all the services you will implement. You can also create new services at this time. Save your new device file and service files as D_[some name] and S_[some name]. This is not mandated by UPnP, but is a convention we use so it's easy to recognize the file type by the name.<br />
<br />
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.<br />
<br />
To add the device to Vera's configuration file, so Vera will load and use the device, go to the Devices tab and at the bottom fill in the UPnP Device filename in the 'Add Device' box and pick a room. When you save your changes, which causes the Luup engine to reload the new configuration, it will look for the device, service and implementation files to start the device. If the files don't already exist on Vera, Vera will log an error and will not start the device. So you need to upload any new files you created by going to the Devices, Luup plugins page in Vera's setup web page. You can upload several files at once. The files will not be used until the Loop engine is reset so you probably want to check the "Restart Luup after upload" box before you click 'go'. If you upload the files without checking the box and want to restart the Luup engine, just click 'save', even if the button is grayed out. You will likely need to make several changes to the files before they're right so you can leave one web browser open to the 'Luup plugin' page and just modify the files in your text editor then click 'go' again to re-upload them after saving your changes. You can open another browser window or tab to access other pages in Vera's web ui and control the device while leaving the list of files to upload on the Luup plugin page intact so you can re-upload by clicking 'go'.<br />
<br />
Next you'll want to know how to debug your Luup plugins and Lua code. See [[Luup_Debugging]]</div>A-lurkerhttp://wiki.mios.com/index.php/Unassigned_devicesUnassigned devices2013-06-17T04:31:52Z<p>A-lurker: </p>
<hr />
<div>[[Category:User Instructions]][[Category:Obsolete]]<br />
[[Category:UI2]]<br />
[[Image:UnnasignedDevices1.jpg|thumb|512px|center|]]<br />
[[Image:UnnasignedDevices2.jpg|thumb|512px|center|]]<br />
[[Image:UnnasignedDevices3.jpg|thumb|512px|center|caption text| Unassigned devices ]]<br />
<br />
<br />
When you have recently added devices, but have not yet added them to a room, Vera will not let you continue until you tell Vera what room they are in. For an explanation and instructions [[Devices#Unassigned_devices|click here]]</div>A-lurkerhttp://wiki.mios.com/index.php/Sensor_StateSensor State2013-06-17T04:31:04Z<p>A-lurker: </p>
<hr />
<div>[[Category:User Instructions]][[Category:Obsolete]]<br />
[[Image:sensor-state.png]]<br />
<br />
The green/red dot next to Arm/Bypass buttons IS NOT the indication whether the sensor is armed or not! It shows the current tripped state of the sensor - Green=Idle, Red=Tripped.<br />
<br />
The only indication whether the sensor is armed or not is to check which button (Arm or Bypass) is highlighted in blue.<br />
<br />
Pretty much all sensors go from green to red (i.e. tripped) almost instantly - motion detected, door/window open, water leak detected etc.<br />
<br />
But going back from red to green (i.e. "untripped", reset or go back to idle) is another story. Some sensors need to be manually reset (water leak sensors, like FortrezZ), some get untripped instantly (door/window closed) or some take time to idle-out (motion sensor when it does not detect motion for some period of time, like 20 minutes by default in HSM100).<br />
<br />
Note: The default idle value of 20 minutes in HSM100 can be changed, see [[ExpressControls3in1]])</div>A-lurkerhttp://wiki.mios.com/index.php/ITempITemp2013-06-17T04:29:38Z<p>A-lurker: </p>
<hr />
<div>[[Category:Hardware]]<br />
iTEMP is a complex device with Temperature sensor, binary sensor and even thermostat functions. To fully support iTEMP in VERA the following steps are required: <br />
<br />
*Make sure you have at least version 1.5.622 on your unit <br />
*Go to Apps -&gt; Install Apps <br />
*Login if needed <br />
*Search for "iTemp" and install it <br />
*After the plugin is installed a new generic device will be installed<br />
<br />
[[Image:Generic-plugin.png]] <br />
<br />
*Go to the Advanced tab and locate the '''iTemp_Devices''' variable<br />
<br />
[[Image:Itemp variable.png]] <br />
<br />
*Fill in the Device #ID of the physical iTemp device, which you already included in the network <br />
*A new child device will be created which you can use in scenes and triggers<br />
<br />
[[Image:Binary_device.png]]</div>A-lurkerhttp://wiki.mios.com/index.php/IR_BlasterIR Blaster2013-06-17T04:29:18Z<p>A-lurker: </p>
<hr />
<div>[[Category:Hardware]]<br />
__FORCETOC__ <br />
<br />
== Add a new IR&nbsp;device ==<br />
<br />
First you will need to connect the IR Blaster in one of the USB ports of the Vera unit, you will need to login on your Dashboard, then go to Devices -&gt; Add Devices, click 'Add' next to 'Add IR device'. <br />
<br />
After that you will need to follow these steps: <br />
<br />
1. Click 'OK' <br />
<br />
[[Image:Usbuirt-1.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
2. Select the IR Blaster <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-2.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
3. Click 'Guided setup' <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-3.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
4. Click on the manufacturer of the device <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-4.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
5. Click on the type of the device (when you click the device you will see it highlighted with a red colour), then click 'Next' <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-5.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
6. Click 'OK' <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-6.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
7. Then the guided setup will let you test infrared codesets starting from the first, if your device responds from cliking on the buttons, then you will need to click 'Yes'. <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-8.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
8. Enter the name of the device and select the room you want, then click 'Next' <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-9.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
9. The device will be added and you will see the following message. You will need to click 'OK' <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-10.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
To be able to control your device, on your Dashboard, you will need to go to Devices -&gt; AV Gear, and click the first icon on the left, under the Actions column (it's the first icon near the name of your AV device). <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-11.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
== Learn IR codes ==<br />
<br />
Make sure that the the IR Blaster is connected in one of the USB ports of the Vera unit, then go to your Dashboard, then go to Devices -&gt; Add Devices, click 'Add' next to 'Add IR device'. <br />
<br />
After that you will need to follow these steps: <br />
<br />
1. Click 'OK' <br />
<br />
[[Image:Usbuirt-1.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
2. Select the IR Blaster <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-2.jpg|thumb|none|300px]] <br />
<br />
3.Select Auto-learning option <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-12.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
4. Then with the remote at 2 inches (4 cm) away from the IR&nbsp;Blaster press and hold one of the most commonly used buttons, like play/rewind/power/volume and wait until the bars fill up <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-13.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
5. Once the code is detected Vera will save and prompt you with this message where you should select the device that was detected <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-14.jpg|thumb|none|300px]] <br />
<br />
<br> <br />
<br />
''Note: If the device is not detected correctly, try the steps again and press another button.'' <br />
<br />
== Re-learn buttons ==<br />
<br />
If some of the buttons don't work you can try to re-learn them using your IR Blaster. To do that follow this guide: <br />
<br />
1. Select the remote you want the buttons changed <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-15.jpg|thumb|none|300px|Usbuirt-15.jpg]] <br />
<br />
<br> <br />
<br />
2. Once the remote loads up, test the buttons and see which do not work, and then press the Setup button <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-16.jpg|thumb|none|300px|Usbuirt-16.jpg]] <br />
<br />
<br> <br />
<br />
3. Then continue with the guide and press the Program mode and select the button you want to re-learn<br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-17.jpg|thumb|none|300px|Usbuirt-17.jpg]] <br />
<br />
<br> <br />
<br />
4. Then on the new screen press on the Learn new code button <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-18.jpg|thumb|none|300px|Usbuirt-18.jpg]] <br />
<br />
<br> <br />
<br />
5. Next you will have to go with the remote to 2 inches (4cm) away of the IR Blaster and press and hold the button you want to relearn and wait until Vera detects the new code. <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-19.jpg|thumb|none|300px|Usbuirt-19.jpg]] <br />
<br />
<br> <br />
<br />
6. Once Vera finishes you will be able to test it to see if it works, and save it by pressing the OK,use it button <br />
<br />
<br> <br />
<br />
[[Image:Usbuirt-20.jpg|thumb|none|300px|Usbuirt-20.jpg]] <br />
<br />
<br></div>A-lurker