Luup Plugins ByHand

From MiOS
Revision as of 17:16, 12 June 2009 by Micasaverde (Talk | contribs)

Jump to: navigation, search

Contents

Description of the XML files

Because the web generator is not yet operational 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. 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

We recommend looking at the file I_TestIR.xml for a sample of a typical a/v device, like a TV, for the device file D_TestIR.xml. I_TestSerial.xml has a typical serial device, like a TV with an RS232 serial port for home automation, for the device file D_TestSerial.xml. 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 nodes:

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. In this sense, the protocol basically just tells Luup what is considered a single "chunk" of data. For example, the protocol 'crlf' means all incoming commands are terminated with a carriage return+line feed character, and all outgoing data should have a cr+lf appended. In this way when data comes in you don't need to worry about parsing it byte-by-byte. Luup will get a "chunk" (one line in this case) and pass it to whatever Lua code you write to handle incoming data. Other protocols are stx+etx if chunks of data are surrounded by those characters, and so on. Since your Lua code is much cleaner when it handles things in chunks, if you have a protocol that we don't yet support which is likely to be used by other devices too, let us know and we'll add it to the Luup engine so you don't need to mess with it.

ioPort: If the device talks to another device on the internet, this is what port to use.

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 i/r 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.

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.

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.

There are several different nodes you can put within the "action" node.

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.

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, and 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 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'.

incoming

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.

Walkthrough to create a device

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.

When you are done editing your files

Personal tools