1. Plugin structure
A Luup plugin is composed of several types of files that can be broken into two groupings - some of the files are optional:Mandatory files:
- D_GenericPlugin1.xml;
- D_GenericPlugin1.json;
- I_GenericPlugin1.xml;
- S_GenericPlugin1.xml;
- L_GenericPlugin1.xml;
- J_GenericPlugin1.xml.
1.1 Files to manage the creation and execution of code in Vera
- D_GenericPlugin1.xml: one device description file
- S_GenericPlugin1.xml: zero or more service files
- I_GenericPlugin1.xml: one device implementation file
- L_GenericPlugin1.xml: zero or more Lua files:
1.2 Files to help manage User Interface (UIx) executed in the web browser
- D_GenericPlugin1.json: zero or one device interface file
- J_GenericPlugin1.xml: zero or more JavaScript files
1.3 Communicating with your plugin
UI Notes has a good summary on communicating with Vera and/or Vera plugins- you can access your plugin via Vera scenes and also through the User Interface assuming a D_GenericPlugin1.json is part of your plugin;
- you can also use Luup Requests. In particular, have a look the web page at VERAs_IP_address:3480/data_request?id=lu_invoke
- via the MIOS servers - have a look at UI Simple
1.4 Caution
- 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.
- 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: & < > " '
- 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.
- 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.
1.5 Example – Somfy Plugin Walkthrough
The following example is for developers wanting to learn how to write a plugin. If you simply want to use the Somfy plugin see: Somfy_Plugin .NOTE: Because this plugin is used for controlling the Somfy window coverings and Vera have a default window coverings device, Somfy plugin will use the same interface file(D_WindowCovering1.json) as default device. Somfy plugin and default window covering device files can be found here.
This example 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: [1]) 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.
1.5.1 Step 1: SSH to Vera
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.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.
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: [2]. 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 # Can't find the root password?.
When you see login as:, enter: root and then enter your password. Now from the root@HomeControl:# prompt type:
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.
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.
1.5.2 Step 2: Add your device by building the XML file by hand
To do this you'll want a good text editor. I'm using, such as Notepad++ available here: [3] 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.In Vera's setup UI, go to Devices, Luup plugins, click 'Luup files' and download the files:
- 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.
- 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.
I_TestSerial.xml because this is a sample implementation for our serial device.
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:
<protocol>raw</protocol>
<handleChildren>1</handleChildren>
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'.
Change the filename in the implementationFile tag from I_TestSerial.xml to I_SomfyBlinds.xml.
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:
function lug_startup(lul_device)
luup.log("Somfy blind #" .. lul_device .. " starting up with ID " .. lug_device[lul_device].ID)
end
and in the 'startup' xml tag, put: lug_startup, like this:
<startup>lug_startup</startup>
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.
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.
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'.
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'
1.5.3 Step 3: Setup the port
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:1. Get the UC232R-10 usb->serial adapter here: [4] 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.
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.
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 aud/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.
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'.
1.5.4 Step 4: Testing the port
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.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:
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 '^50\|^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'.
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.
1.5.5 Step 5: Create child devices for the blinds
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'.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:
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)
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.
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:
function lug_startup(lul_device)
local lul_ID="01,02"
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1","BlindIds", lul_ID, lul_device)
luup.log("test2--Somfy blind #" .. lul_device .. " starting up with ID " .. lul_ID)
end
lug_startup(123) - 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:
function some_test()
luup.log("some_test")
end
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:
function some_test()
luup.log("some_test2")
end
some_test()
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:
function lug_startup(lul_device)
child_devices = luup.chdev.start(lul_device);
for i = 1,16 do
s = string.format("%02d", i)
luup.log("Adding blind " .. s)
luup.chdev.append(lul_device, child_devices, s, "Blind #" .. s, "urn:schemas-upnp-org:device:BinaryLight:1", "D_BinaryLight1.xml", "", "", true)
end
luup.chdev.sync(lul_device, child_devices)
end
lug_startup(123)
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 [5]).
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
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.
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.
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.
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.
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:
function lug_startup(lul_device)
local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_device)
if (lul_ID == nil) then
lul_ID = "01,02"
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)
end
local lul_prefix = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_device)
if (lul_prefix == nil) then
lul_prefix = "01"
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_prefix, lul_device)
end
luup.log("Somfy ID is " .. lul_ID .. " prefix is " .. lul_prefix)
child_devices = luup.chdev.start(lul_device);
for i = 1,16 do
s = string.format("%02d", i)
if (string.find(lul_ID,s) ~= nil) then
luup.log("Adding blind " .. s)
luup.chdev.append(lul_device, child_devices, s, "Blind #" .. s, "urn:schemas-upnp-org:device:BinaryLight:1", "D_BinaryLight1.xml", "", "", false)
end
end
luup.chdev.sync(lul_device, child_devices)
end
lug_startup(123)
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.
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.
Every time you re-start the Luup engine, such as clicking 'Save', you'll see in the logs this startup sequence.
1.5.6 Step 6: Polish up the startup sequence
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.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:
local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_device)
if (lul_ID == nil) then
lul_ID = "01,02"
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)
end
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:
if (luup.io.is_connected(lul_device) == false) then
luup.log("No port for Somfy", 1)
luup.task("Choose the Serial Port for the URTSI", 2, "Somfy Blind Interface", -1)
return false
end
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.
So now the startup sequence looks like this:
function lug_startup(lul_device)
local lul_ID = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_device)
if (lul_ID == nil) then
lul_ID = "01,02"
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "BlindIds", lul_ID, lul_device)
end
local lul_prefix = luup.variable_get("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_device)
if (lul_prefix == nil) then
lul_prefix = "01"
luup.variable_set("urn:micasaverde-com:serviceId:SomfyBlinds1", "UrtsiId", lul_prefix, lul_device)
end
luup.log("Somfy ID is " .. lul_ID .. " prefix is " .. lul_prefix)
if (luup.io.is_connected(lul_device) == false) then
luup.log("No port for Somfy", 1)
luup.task("Choose the Serial Port for the URTSI",2, "Somfy Blind Interface", -1)
return false
end
child_devices = luup.chdev.start(lul_device);
for i = 1,16 do
s = string.format("%02d", i)
if (string.find (lul_ID,s) ~= nil) then
luup.log("Adding blind " .. s)
luup.chdev.append(lul_device, child_devices, s, "Blind #" .. s,
"urn:schemas-micasaverde-com:device:WindowCovering:1", "D_WindowCovering1.xml", "", "", false)
end
end
luup.chdev.sync(lul_device, child_devices)
end
1.5.7 Step 7: Implement the actions in the xml file
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:<actionList> <action> <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId> <name>SetTarget</name> <run> local lul_command = lul_prefix .. luup.devices[lul_device].id .. "U\r" local lul_reverse = luup.variable_get("urn:micasaverde-com:serviceId:HaDevice1", "ReverseOnOff", lul_device) if (lul_settings.newTargetValue == "1" or (lul_settings.newTargetValue == "0" and lul_reverse == "1")) then lul_command = lul_prefix .. luup.devices[lul_device].id .. "D\r" end if (luup.io.write(lul_command) == false) then luup.log("cannot send: " .. tostring(lul_command),1) luup.set_failure(true) return false end </run> </action> </actionList>
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.
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.
Addendum (Upcoming Security Enhancements)
Making plugins work with Secure My Vera enabled.
As part of our ongoing commitment to platform hardening and robustness, we are deploying several security enhancements in the coming months. Beginning with our 7.27 release, all Vera controllers will be secured by default, which means that:
- Ports 22, 80, 443, 3480, and 49451 will be blocked on all the network interfaces, except the local loopback interface (localhost / 127.0.0.1).
- The RunLua action will be disabled, by default
Below are the most common plugin issues encountered after this upcoming security enhancement, and the proposed solutions:
- Issue: data_request?id=action&action=RunLua&Code=luup.variable_set(...)
- Issue: data_request?id=action&action=RunLua&Code=luup.call_action(...)
- Issue: requests on port 3480 / 49451
Issue: data_request?id=action&action=RunLua&Code=luup.variable_set(...)
Since the RunLua action will be disabled, that code will no longer work.Solution
Replace that RunLua code with the variableset data_request.Example
http://127.0.0.1/port_3480/data_request?id=action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunLua&Code=luup.variable_set("urn:micasaverde-com:serviceId:MyServiceId", "DebugMode", "1", 18)
must be rewritten as:
http://127.0.0.1/port_3480/data_request?id=variableset&DeviceNum=18&serviceId=urn:micasaverde-com:serviceId:MyServiceId&Variable=DebugMode&Value=1
Issue: data_request?id=action&action=RunLua&Code=luup.call_action(...)
This issue is also caused by disabling the RunLua action.Solution
Replace the RunLua code with the action data_request.Example
http://127.0.0.1/port_3480/data_request?id=action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunLua&Code=luup.call_action("urn:micasaverde-com:serviceId:MyServiceId", "SetDebugMode", { Mode = "1" }, 18)
Must be rewritten as:
http://127.0.0.1/port_3480/data_request?id=action&DeviceNum=18&serviceId=urn:micasaverde-com:serviceId:MyServiceId&action=SetDebugMode&Mode=1
Issue: requests on port 3480 / 49451
This issue affects only requests made by a network device, meaning they are coming to Vera on a LAN or WAN interface.Solution
This issue can't be fully fixed by the plugin developers, however, it's necessary that all the data_requests are made on port_3480, and NOT directly on port 3480, or on port 49451, or on port_49451.So, any request that looks like this:
http://VERA_IP:3480/data_request?id=SOME_VALUE
http://VERA_IP:49451/data_request?id=SOME_VALUE
http://VERA_IP/port_49451/data_request?id=SOME_VALUE
Must be rewritten to look like this:
http://VERA_IP/port_3480/data_request?id=SOME_VALUE
- - - - -
2. Document links
- Luup UPNP Files:http://wiki.mios.com/index.php/Luup_UPNP_Files#Device_Files
- Luup Devices:
http://wiki.mios.com/index.php/Luup_Devices
- Luup UPnP Variables and Actions:
http://wiki.mios.com/index.php/Luup_UPnP_Variables_and_Actions
- Luup Plugins ByHand:
http://wiki.mios.com/index.php/Luup_Plugins_ByHand
- Luup Declarations:
http://wiki.mios.com/index.php/Luup_Declarations
- Luup Lua extensions:
http://wiki.mios.com/index.php/Luup_Lua_extensions
- Static JSON file:
http://wiki.mios.com/index.php/Luup_plugins:_Static_JSON_file
- Luup plugin tabs:
http://wiki.mios.com/index.php/Luup_plugin_tabs
- Luup plugin icons:
http://wiki.mios.com/index.php/Luup_plugin_icons
- Luup Requests:
http://wiki.mios.com/index.php/Luup_Requests
- Logon Vera SSH:
http://wiki.mios.com/index.php/Logon_Vera_SSH#Can.27t_find_the_root_password.3F
- UI Notes:
http://wiki.mios.com/index.php/UI_Notes
- UI Simple:
http://wiki.mios.com/index.php/UI_Simple
- Somfy Plugin usage information:
http://wiki.mios.com/index.php/Somfy_Plugin.
Utilities:
- Somfy plugin and default window coverings device files:http://download.mios.com/fixes/Somfy.zip
- Lua 5.1 Reference Manual:
http://www.lua.org/manual/5.1/manual.html
- PuTTY Download Page:
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
- Notepad++
http://notepad-plus-plus.org/
- RS232 to RTS interface:
http://www.blindshademotors.com/documents/accessories-special-applications/rs232-to-rts-compatability.pdf
- D_TestSerial download:
http://wiki.mios.com/images/5/50/D_TestSerial.zip
- I_TestSerial download:
http://wiki.mios.com/images/f/fd/I_TestSerial.zip
- Future electronics:
http://www.futureelectronics.com/WebsiteLanding.aspx
Comments
0 comments
Please sign in to leave a comment.