Luup Plugins ByHand
(→) |
(→) |
||
Line 30: | Line 30: | ||
*<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. | *<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. | ||
− | Caution: | + | 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. |
− | Hint: | + | Hint: with verbose logging enabled - log lines starting with "52" are RX data, those with "51" are TX data. |
=== <ioPort> === | === <ioPort> === |
Revision as of 23:41, 3 February 2014
Contents |
Description of the XML files
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.
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.
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.
The controlURL and eventSubURL are set by the Luup engine and the values in the device specification file are ignored.
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.
The Luup XML implementation file
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:
<settings>
The settings node contains various settings for the implementation.
<protocol>
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 <incoming> requests. 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.
Valid values for this tag are:
- cr - 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.
- crlf - 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.
- stxetx - 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 "<stx>test<etx>" is received, the framework will strip the STX and ETX and pass the string "test" to your incoming data handler.
- raw - 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 'raw' and let us add support for your protocol if you have a new one we don't yet support.
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.
Hint: with verbose logging enabled - log lines starting with "52" are RX data, those with "51" are TX data.
<ioPort>
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.
<handleChildren>
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.
<functions>
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.
<files>
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>
<startup>
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.
<actionList>
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.
function declarations
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.
There are several different nodes you can put within the "action" node.
<run/job/incoming/timeout>
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.
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.
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.
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:
1) the status of the job
2) how long to wait before the job times out in seconds.
The status can be:
- 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
- 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.
- 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.
- 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.
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.
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.
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.
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'.
Luup Declarations lists what variables this Lua code receives and what it should return.
<incoming>
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.
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.
Luup Declarations lists what variables this Lua code receives and what it should return.
<ir>
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 IR tag
Walkthrough to create a device
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: [1]), which is a simple 1-way serial device (ie send data, but don't get any response) here: Luup_Somfy_Walkthrough
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.
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.
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 -.
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.
Now refer to "The Luup XML implementation file" section above to learn how to create an implementation file.
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'.
Next you'll want to know how to debug your Luup plugins and Lua code. See Luup_Debugging